/*======================================================================
  FILE: icalerror.h
  CREATOR: eric 09 May 1999

 (C) COPYRIGHT 2000, Eric Busboom <eric@civicknowledge.com>

 This library is free software; you can redistribute it and/or modify
 it under the terms of either:

    The LGPL as published by the Free Software Foundation, version
    2.1, available at: https://www.gnu.org/licenses/lgpl-2.1.html

 Or:

    The Mozilla Public License Version 2.0. You may obtain a copy of
    the License at https://www.mozilla.org/MPL/

 The original code is icalerror.h
======================================================================*/

#ifndef ICALERROR_H
#define ICALERROR_H

#include "libical_ical_export.h"
#include <assert.h>
#include <stdio.h>

/**
 * @file icalerror.h
 * @brief Error handling for libical
 *
 * Most routines will set the global error value ::icalerrno on errors.
 * This variable is an enumeration; permissible values can be found in
 * icalerror.h. If the routine returns an enum ::icalerrorenum, then the
 * return value will be the same as ::icalerrno. You can use icalerror_strerror()
 * to get a string that describes the error, or icalerror_perror() to
 * get a string describing the current error set in ::icalerrno.
 */

#define ICAL_SETERROR_ISFUNC

/**
 * @brief Triggered before any error is called
 *
 * This routine is called before any error is triggered.
 * It is called by icalerror_set_errno(), so it does not
 * appear in all of the macros below.
 *
 * This routine can be used while debugging by setting
 * a breakpoint here.
 */
LIBICAL_ICAL_EXPORT void icalerror_stop_here(void);

/**
 * @brief Triggered to abort the process
 *
 * This routine is called to abort the process in the
 * case of an error.
 */
LIBICAL_ICAL_EXPORT void icalerror_crash_here(void);

#ifndef _MSC_VER
#pragma GCC visibility push(default)
#endif
/**
 * @typedef icalerrorenum
 * @enum icalerrorenum
 * @brief Represents the different types of errors that
 *  can be triggered in libical
 *
 * Each of these values represent a different type of error, which
 * is stored in ::icalerrno on exit of the library function (or
 * can be returned, but if it is, ::icalerrno is also set).
 */
typedef enum icalerrorenum
{
    /** No error happened. */
    ICAL_NO_ERROR = 0,

    /** A bad argument was passed to a function. */
    ICAL_BADARG_ERROR,

    /** An error occurred while creating a new object with a `*_new()` routine. */
    ICAL_NEWFAILED_ERROR,

    /** An error occurred while allocating some memory. */
    ICAL_ALLOCATION_ERROR,

    /** Malformed data was passed to a function. */
    ICAL_MALFORMEDDATA_ERROR,

    /** An error occurred while parsing part of an iCal component. */
    ICAL_PARSE_ERROR,

    /** An internal error happened in library code. */
    ICAL_INTERNAL_ERROR, /* Like assert --internal consist. prob */

    /** An error happened while working with a file. */
    ICAL_FILE_ERROR,

    /** Failure to properly sequence calls to a set of interfaces. */
    ICAL_USAGE_ERROR,

    /** An unimplemented function was called. */
    ICAL_UNIMPLEMENTED_ERROR,

    /** An unknown error occurred. */
    ICAL_UNKNOWN_ERROR  /* Used for problems in input to icalerror_strerror() */
} icalerrorenum;
#ifndef _MSC_VER
#pragma GCC visibility pop
#endif

/**
 * @brief Returns the current ::icalerrno value
 * @return A pointer to the current ::icalerrno value
 *
 * Yields a pointer to the current ::icalerrno value. This can
 * be used to access (read from and write to) it.
 *
 * ### Examples
 * ```c
 * assert(*icalerrno_return() == icalerrno);
 * ```
 */
LIBICAL_ICAL_EXPORT icalerrorenum *icalerrno_return(void);

/**
 * @brief Access the current ::icalerrno value
 * @return The current ::icalerrno value
 * @note Pseudo-variable that can be used to access the current
 *  ::icalerrno.
 *
 * ### Usage
 * ```c
 * if(icalerrno == ICAL_PARSE_ERROR) {
 *     // ...
 * }
 *
 * // resets error
 * icalerrno = ICAL_NO_ERROR;
 * ```
 */
#define icalerrno (*(icalerrno_return()))

/**
 * @brief Change if errors are fatal
 * @param fatal If true, libical aborts after a call to icalerror_set_error()
 * @warning NOT THREAD SAFE: it is recommended that you do not change
 *  this in a multithreaded program.
 *
 * ### Usage
 * ```c
 * icalerror_set_errors_are_fatal(true); // default
 * icalerror_set_errors_are_fatal(false);
 * ```
 */
LIBICAL_ICAL_EXPORT void icalerror_set_errors_are_fatal(int fatal);

/**
 * @brief Determine if errors are fatal
 * @return True if libical errors are fatal
 *
 * ### Usage
 * ```c
 * if(icalerror_get_errors_are_fatal()) {
 *     // since errors are fatal, this will abort the
 *     // program.
 *     icalerror_set_errno(ICAL_PARSE_ERROR);
 * }
 * ```
 */
LIBICAL_ICAL_EXPORT int icalerror_get_errors_are_fatal(void);

/* Warning messages */

/**
 * @def icalerror_warn(message)
 * @brief Prints a formatted warning message to stderr
 * @param message Warning message to print
 *
 * ### Usage
 * ```c
 * icalerror_warn("Non-standard tag encountered");
 * ```
 */

#ifdef __GNUC__ca
#define icalerror_warn(message) \
{fprintf(stderr, "%s(), %s:%d: %s\n", __FUNCTION__, __FILE__, __LINE__, message);}
#else /* __GNU_C__ */
#define icalerror_warn(message) \
{fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, message);}
#endif /* __GNU_C__ */

/**
 * @brief Resets icalerrno to ::ICAL_NO_ERROR
 *
 * ### Usage
 * ```c
 * if(icalerrno == ICAL_PARSE_ERROR) {
 *     // ignore parsing errors
 *     icalerror_clear_errno();
 * }
 * ```
 */
LIBICAL_ICAL_EXPORT void icalerror_clear_errno(void);

/**
 * @enum icalerrorstate
 * @typedef icalerrorstate
 * @brief Determine if an error is fatal or non-fatal.
 */
typedef enum icalerrorstate
{
    /** Fatal. */
    ICAL_ERROR_FATAL,

    /** Non-fatal. */
    ICAL_ERROR_NONFATAL,

    /** Fatal if icalerror_errors_are_fatal(), non-fatal otherwise. */
    ICAL_ERROR_DEFAULT,

    /** Asked state for an unknown error type. */
    ICAL_ERROR_UNKNOWN
} icalerrorstate;

/**
 * @brief Finds the description string for error
 * @param e The type of error that occurred
 * @return A string describing the error that occurred
 *
 * @par Error handling
 * If the type of error @a e wasn't found, it returns the description
 * for ::ICAL_UNKNOWN_ERROR.
 *
 * @par Ownership
 * The string that is returned is owned by the library and must not
 * be free'd() by the user.
 *
 * ### Usage
 * ```c
 * if(icalerrno != ICAL_NO_ERROR) {
 *     printf("%s\n", icalerror_strerror(icalerrno));
 * }
 * ```
 */
LIBICAL_ICAL_EXPORT const char *icalerror_strerror(icalerrorenum e);

/**
 * @brief Returns the description string for the current error in ::icalerrno
 *
 * @par Error handling
 * If the type of error @a e wasn't found, it returns the description
 * for ::ICAL_UNKNOWN_ERROR.
 *
 * @par Ownership
 * The string that is returned is owned by the library and must not
 * be free'd() by the user.
 *
 * ### Usage
 * ```c
 * if(icalerrno != ICAL_NO_ERROR) {
 *     printf("%s\n", icalerror_perror());
 * }
 * ```
 */
LIBICAL_ICAL_EXPORT const char *icalerror_perror(void);

/**
 * @brief Prints backtrace
 * @note Only works on systems that support it (HAVE_BACKTRACE enabled).
 *
 * ### Usage
 * ```
 * if(icalerrno != ICAL_NO_ERROR) {
 *     ical_bt();
 * }
 * ```
 */
LIBICAL_ICAL_EXPORT void ical_bt(void);

/**
 * @brief Sets the ::icalerrorstate for a given ::icalerrorenum @a error
 * @param error The error to change
 * @param state The new error state of the error
 *
 * Sets the severity of a given error. For example, it can be used to
 * set the severity of an ::ICAL_PARSE_ERROR to be an ::ICAL_ERROR_NONFATAL.
 *
 * ### Usage
 * ```c
 * icalerror_set_error_state(ICAL_PARSE_ERROR, ICAL_ERROR_NONFATAL);
 * ```
 */
LIBICAL_ICAL_EXPORT void icalerror_set_error_state(icalerrorenum error, icalerrorstate state);

/**
 * @brief Gets the error state (severity) for a given error
 * @param error The error to examine
 * @return Returns the severity of the error
 */
LIBICAL_ICAL_EXPORT icalerrorstate icalerror_get_error_state(icalerrorenum error);

/**
 * @brief Reads an error from a string
 * @param str The error name string
 * @return An ::icalerrorenum representing the error
 *
 * @par Error handling
 * If the error specified in @a str can't be found, instead
 * ::ICAL_UNKNOWN_ERROR is returned.
 *
 * ### Usage
 * ```c
 * assert(icalerror_error_from_string("PARSE") == ICAL_PARSE_ERROR);
 * assert(icalerror_error_from_string("NONSENSE") == ICAL_UNKNOWN_ERROR);
 * ```
 */
LIBICAL_ICAL_EXPORT icalerrorenum icalerror_error_from_string(const char *str);

/**
 * @def icalerror_set_errno(x)
 * @brief Sets the ::icalerrno to a given error
 * @param x The error to set ::icalerrno to
 *
 * Sets ::icalerrno to the error given in @a x. Additionally, if
 * the error is an ::ICAL_ERROR_FATAL or if it's an ::ICAL_ERROR_DEFAULT
 * and ::ICAL_ERRORS_ARE_FATAL is true, it prints a warning to @a stderr
 * and aborts the process.
 *
 * ### Usage
 * ```c
 * icalerror_set_errno(ICAL_PARSE_ERROR);
 * ```
 */
#if !defined(ICAL_SETERROR_ISFUNC)
#define icalerror_set_errno(x) \
icalerrno = x; \
if(icalerror_get_error_state(x) == ICAL_ERROR_FATAL || \
   (icalerror_get_error_state(x) == ICAL_ERROR_DEFAULT && \
    icalerror_get_errors_are_fatal() == 1)){              \
   icalerror_warn(icalerror_strerror(x)); \
   ical_bt(); \
   assert(0); \
} }
#else
/**
 * @brief Sets the ::icalerrno to a given error
 * @param x The error to set ::icalerrno to
 *
 * Sets ::icalerrno to the error given in @a x. Additionally, if
 * the error is an ::ICAL_ERROR_FATAL or if it's an ::ICAL_ERROR_DEFAULT
 * and ::ICAL_ERRORS_ARE_FATAL is true, it prints a warning to @a stderr
 * and aborts the process.
 *
 * ### Usage
 * ```c
 * icalerror_set_errno(ICAL_PARSE_ERROR);
 * ```
 */
LIBICAL_ICAL_EXPORT void icalerror_set_errno(icalerrorenum x);
#endif

/**
 * @def ICAL_ERRORS_ARE_FATAL
 * @brief Determines if all libical errors are fatal and lead to
 *  the process aborting.
 *
 * If set to 1, all libical errors are fatal and lead to the
 * process aborting upon encountering on. Otherwise, errors
 * are nonfatal.
 *
 * Can be checked with libical_get_errors_are_fatal().
 */

#if !defined(ICAL_ERRORS_ARE_FATAL)
#define ICAL_ERRORS_ARE_FATAL 0
#endif

#if ICAL_ERRORS_ARE_FATAL == 1
#undef NDEBUG
#endif

#define icalerror_check_value_type(value,type);
#define icalerror_check_property_type(value,type);
#define icalerror_check_parameter_type(value,type);
#define icalerror_check_component_type(value,type);

/* Assert with a message */
/**
 * @def icalerror_assert(test, message)
 * @brief Assert with a message
 * @param test The assertion to test
 * @param message The message to print on failure of assertion
 *
 * Tests the given assertion @a test, and if it fails, prints the
 * @a message given on @a stderr as a warning and aborts the process.
 * This only works if ::ICAL_ERRORS_ARE_FATAL is true, otherwise
 * does nothing.
 */
#if ICAL_ERRORS_ARE_FATAL == 1

#ifdef __GNUC__
#define icalerror_assert(test,message) \
if (!(test)) { \
    fprintf(stderr, "%s(), %s:%d: %s\n", __FUNCTION__, __FILE__, __LINE__, message); \
    icalerror_stop_here(); \
    abort();}
#else /*__GNUC__*/
#define icalerror_assert(test,message) \
if (!(test)) { \
    fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, message); \
    icalerror_stop_here(); \
    abort();}
#endif /*__GNUC__*/

#else /* ICAL_ERRORS_ARE_FATAL */
#define icalerror_assert(test,message)
#endif /* ICAL_ERRORS_ARE_FATAL */

/**
 * @brief Checks the assertion @a test and raises error on failure
 * @param test The assertion to check
 * @param arg  The argument involved (as a string)
 *
 * This function checks the assertion @a test, which is used to
 * test if the parameter @a arg is correct. If the assertion fails,
 * it sets ::icalerrno to ::ICAL_BADARG_ERROR.
 *
 * ### Example
 * ```c
 * void test_function(icalcomponent *component) {
 *    icalerror_check_arg(component != 0, "component");
 *
 *    // use component
 * }
 * ```
 */
#define icalerror_check_arg(test,arg) \
if (!(test)) { \
    icalerror_set_errno(ICAL_BADARG_ERROR); \
}

/**
 * @brief Checks the assertion @a test and raises error on failure, returns void
 * @param test The assertion to check
 * @param arg  The argument involved (as a string)
 *
 * This function checks the assertion @a test, which is used to
 * test if the parameter @a arg is correct. If the assertion fails,
 * it sets ::icalerrno to ::ICAL_BADARG_ERROR and causes the enclosing
 * function to return `void`.
 *
 * ### Example
 * ```c
 * void test_function(icalcomponent *component) {
 *    icalerror_check_arg_rv(component != 0, "component");
 *
 *    // use component
 * }
 * ```
 */
#define icalerror_check_arg_rv(test,arg) \
if (!(test)) { \
    icalerror_set_errno(ICAL_BADARG_ERROR); \
    return; \
}

/**
 * @brief Checks the assertion @a test and raises error on failure, returns 0
 * @param test The assertion to check
 * @param arg  The argument involved (as a string)
 *
 * This function checks the assertion @a test, which is used to
 * test if the parameter @a arg is correct. If the assertion fails,
 * it sets ::icalerrno to ::ICAL_BADARG_ERROR and causes the enclosing
 * function to return `0`.
 *
 * ### Example
 * ```c
 * int test_function(icalcomponent *component) {
 *    icalerror_check_arg_rz(component != 0, "component");
 *
 *    // use component
 *    return icalcomponent_count_kinds(component, ICAL_ANY_COMPONENT);
 * }
 * ```
 */
#define icalerror_check_arg_rz(test,arg) \
if (!(test)) { \
    icalerror_set_errno(ICAL_BADARG_ERROR); \
    return 0; \
}

/**
 * @brief Checks the assertion @a test and raises error on failure, returns @a error
 * @param test The assertion to check
 * @param arg  The argument involved (as a string)
 * @param error What to return on error
 *
 * This function checks the assertion @a test, which is used to
 * test if the parameter @a arg is correct. If the assertion fails,
 * it aborts the process with `assert(0)` and causes the enclosing
 * function to return @a error.
 *
 * ### Example
 * ```c
 * icalcomponent *test_function(icalcomponent *component) {
 *    icalerror_check_arg_re(component != 0, "component", NULL);
 *
 *    // use component
 *    return icalcomponent_get_first_real_component(component);
 * }
 * ```
 */
#define icalerror_check_arg_re(test,arg,error) \
if (!(test)) { \
    icalerror_stop_here(); \
    assert(0); \
    return error; \
}

/**
 * @brief Checks the assertion @a test and raises error on failure, returns @a x
 * @param test The assertion to check
 * @param arg  The argument involved (as a string)
 * @param x    What to return on error
 *
 * This function checks the assertion @a test, which is used to
 * test if the parameter @a arg is correct. If the assertion fails,
 * it sets ::icalerrno to ::ICAL_BADARG_ERROR and causes the enclosing
 * function to return @a x.
 *
 * ### Example
 * ```c
 * icalcomponent *test_function(icalcomponent *component) {
 *    icalerror_check_arg_rx(component != 0, "component", NULL);
 *
 *    // use component
 *    return icalcomponent_get_first_real_component(component);
 * }
 * ```
 */
#define icalerror_check_arg_rx(test,arg,x) \
if (!(test)) { \
    icalerror_set_errno(ICAL_BADARG_ERROR); \
    return x; \
}

/* String interfaces to set an error to NONFATAL and restore it to its original value */

/**
 * @brief Suppresses a given error
 * @param error The name of the error to suppress
 * @return The previous icalerrorstate (severity)
 *
 * Calling this function causes the given error to be listed as
 * ::ICAL_ERROR_NONFATAL, and thus suppressed. Error states can be
 * restored with icalerror_restore().
 *
 * ### Usage
 * ```c
 * // suppresses internal errors
 * icalerror_supress("INTERNAL");
 * ```
 */
LIBICAL_ICAL_EXPORT icalerrorstate icalerror_supress(const char *error);

/**
 * Assigns the given error the given icalerrorstate (severity).
 * @param error The error in question
 * @param es The icalerrorstate (severity) to set it to
 *
 * Calling the function changes the ::icalerrorstate of the given error.
 *
 * ### Usage
 * ```c
 * // suppress internal errors
 * icalerror_supress("INTERNAL");
 *
 * // ...
 *
 * // restore internal errors
 * icalerror_restore("INTERNAL", ICAL_ERROR_DEFAULT);
 * ```
 */
LIBICAL_ICAL_EXPORT void icalerror_restore(const char *error, icalerrorstate es);

#endif /* !ICALERROR_H */
