/*
 * This file is part of the KMOS Pipeline
 * Copyright (C) 2002,2003 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/**
 * @internal
 * @defgroup kmo_error   Private Error Handling Macros
 *
 * This module provides macros for the handling of CPL errors and the error
 * state.
 *
 * @par General Rules:
 *
 * - The macros below take care of setting the right information about the
 *   location where an error happened. Functions like cpl_error_set_where()
 *   don't need to be called by the programmer using them.
 *
 * @par Synopsis:
 * @code

 * @endcode
 */
/** @{ */

#ifndef KMO_ERROR_H
#define KMO_ERROR_H

/*-----------------------------------------------------------------------------
    Includes
 -----------------------------------------------------------------------------*/

#include <cpl.h>

/*-----------------------------------------------------------------------------
    Defines
 -----------------------------------------------------------------------------*/
/* ToDo: look at the following */
#if defined HAVE_DECL___FUNC__ && !HAVE_DECL___FUNC__
#ifndef __func__
#define __func__ ""
#endif
#endif

/*-----------------------------------------------------------------------------
                                Functional Macros
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
 * @brief Beginning of a TRY-block.
 * @hideinitializer
 *
 * The macro KMO_TRY is to be used like a keyword in front of a deeper
 * scope. This scope has to be followed by the macro @ref KMO_CATCH. This
 * means that KMO_TRY and KMO_CATCH build a frame around a code
 * statement or a code scope, called the try-block.
 *
 * The KMO_CATCH macro is to be followed by a statement or scope, which is
 * only executed if a CPL error is set while reaching the KMO_CATCH macro,
 * called the catch-block.
 *
 * The try-block can be exited by using one of the macros below, for
 * example with @ref KMO_TRY_EXIT_WITH_ERROR(). In this case, a jump to
 * KMO_CATCH is performed, and the catch-block executed if an error is set.
 *
 * @note
 *
 * The following constraints have to be fulfilled:
 * - A "return" or "goto" statement inside the try-block is forbidden, because
 *   leaving the try-block without processing the KMO_CATCH macro will mess
 *   up the error state information. In the catch-block (which comes after
 *   the KMO_CATCH macro), it is allowed.
 * - The macros require some variables, which are declared at the beginning
 *   of the KMO_TRY macro. Therefore it is not possible in ANSI-C to have
 *   code statements (except declarations) before the KMO_TRY macro. If it is
 *   required, this can be solved by putting a scope around the try-catch
 *   construct.
 * - Only one KMO_TRY - KMO_CATCH - construct can be inside one function.
 *
 * @par Example 1:
 *
 * @code
cpl_error_code my_func()
{
    cpl_object      *obj = NULL;
    cpl_error_code  error = CPL_ERROR_NONE;

    KMO_TRY
    {
        KMO_TRY_EXIT_IF_NULL(
            obj = cpl_object_new());

        KMO_TRY_EXIT_IF_ERROR(
            cpl_function(obj));
    }
    KMO_CATCH
    {
        error = cpl_error_get_code();
    }

    cpl_object_delete(obj);

    return error;
}
 * @endcode
 *
 * @par Example 2:
 *
 * @code
cpl_object  *my_func()
{
    cpl_object      *obj = NULL;

    KMO_TRY
    {
        KMO_TRY_EXIT_IF_NULL(
            obj = cpl_object_new());

        KMO_TRY_EXIT_IF_ERROR(
            cpl_function(obj));
    }
    KMO_CATCH
    {
        cpl_object_delete(obj);
        obj = NULL;
    }

    return obj;
}
 * @endcode
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY \
   int             kmo_error_is_set_where = 0, \
                  kmo_error_catch_call_flag; \
   cpl_errorstate  kmo_error_state; \
   \
   kmo_error_state = cpl_errorstate_get(); \
   \
   do

/*----------------------------------------------------------------------------*/
/**
 * @brief   End of a TRY-block, beginning of a CATCH-block.
 * @hideinitializer
 *
 * Please refer to @ref KMO_TRY.
 */
/*----------------------------------------------------------------------------*/
#define KMO_CATCH \
   while (0); \
   \
   goto _KMO_CATCH_LABEL_; /* avoid warning if not used */ \
   _KMO_CATCH_LABEL_: \
   \
   if ((kmo_error_catch_call_flag = \
         !cpl_errorstate_is_equal(kmo_error_state))) \
   { \
      if (!kmo_error_is_set_where) \
            cpl_error_set_where(__func__); \
   } \
   if (kmo_error_catch_call_flag)

/*----------------------------------------------------------------------------*/
/**
 * @brief   Return new CPL error code
 * @hideinitializer
 *
 * @return  If the CPL error state has changed since KMO_TRY, the latest
 *          error code is returned, otherwise CPL_ERROR_NONE.
 *
 * - May be called in catch-block.
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_GET_NEW_ERROR(void) \
   (cpl_errorstate_is_equal(kmo_error_state) ? \
   CPL_ERROR_NONE : \
   cpl_error_get_code() \
   )

/*----------------------------------------------------------------------------*/
/**
 * @brief   Recover the error state which was present during KMO_TRY (at
 *          the beginning of the try-block).
 * @hideinitializer
 *
 * - May be called in catch-block.
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_RECOVER(void) \
   cpl_errorstate_set(kmo_error_state)

/*----------------------------------------------------------------------------*/
/**
 * @brief   Set a new error code.
 * @hideinitializer
 *
 * - If code is CPL_ERROR_NONE, then @ref KMO_TRY_RECOVER() is called.
 * - May be called in catch-block.
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_SET_ERROR(code) \
do { \
    if (code != CPL_ERROR_NONE) \
    { \
        cpl_error_set(__func__, code); \
        kmo_error_is_set_where = __LINE__; \
    } \
    else \
        KMO_TRY_RECOVER(); \
} while (0)

/*----------------------------------------------------------------------------*/
/**
 * @brief   Set a new error code together with a custom error message.
 * @hideinitializer
 *
 * - If code is CPL_ERROR_NONE, then @ref KMCLIPM_ERROR_RECOVER_TRYSTATE() is
 *   called, without setting a message.
 * - May be called outside TRY-block.
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_ERROR_SET_MSG(code, msg) \
do { \
    if (code != CPL_ERROR_NONE) \
    { \
        cpl_error_set_message_macro(        __func__, \
                                            code, \
                                            __FILE__, \
                                            __LINE__, \
                                            msg); \
    } \
    else \
        KMO_TRY_RECOVER(); \
} while (0)

/*----------------------------------------------------------------------------*/
/**
 * @brief   Check the CPL error state, and exit the try-block if not
 *          CPL_ERROR_NONE.
 * @hideinitializer
 *
 * - <b>Forbidden in catch-block!</b>
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_CHECK_ERROR_STATE(void) \
do { \
   if (!cpl_errorstate_is_equal(kmo_error_state)) \
   { \
      cpl_error_set_where(__func__); \
      kmo_error_is_set_where = __LINE__; \
      KMO_TRY_EXIT(); \
   } \
} while (0)

/*----------------------------------------------------------------------------*/
/**
 * @brief   Check the CPL error state, and exit the try-block if not
 *          CPL_ERROR_NONE.
 * @hideinitializer
 *
 * - <b>Forbidden in catch-block!</b>
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_CHECK_ERROR_STATE_MSG(msg) \
do { \
   if (!cpl_errorstate_is_equal(kmo_error_state)) \
   { \
      cpl_error_set_where(__func__); \
      cpl_msg_error(__func__, "%s", msg); \
      kmo_error_is_set_where = __LINE__; \
      KMO_TRY_EXIT(); \
   } \
} while (0)

/*----------------------------------------------------------------------------*/
/**
 * @brief   Set a new CPL error, and exit the try-block.
 * @hideinitializer
 *
 * - @a code must not be CPL_ERROR_NONE!
 * - <b>Forbidden in catch-block!</b>
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_EXIT_WITH_ERROR(code) \
do { \
    cpl_error_set(__func__, code); \
    kmo_error_is_set_where = __LINE__; \
    KMO_TRY_EXIT(); \
} while (0)

/*----------------------------------------------------------------------------*/
/**
 * @brief   If @a function is or returns an error != CPL_ERROR_NONE, then
 *          the try-block is exited.
 * @hideinitializer
 *
 * - It is assumed, that a returned error is already set as the new error state.
 * - <b>Forbidden in catch-block!</b>
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_EXIT_IF_ERROR(function) \
do { \
    if ((function) != CPL_ERROR_NONE) \
    { \
        cpl_error_set_where(__func__); \
        kmo_error_is_set_where = __LINE__ ; \
        KMO_TRY_EXIT(); \
    } \
} while (0)

/*----------------------------------------------------------------------------*/
/**
 * @brief   If @a function is or returns a NULL pointer, then
 *          the try-block is exited.
 * @hideinitializer
 *
 * - It is assumed, that a new error state is already set if @a function is
 *   NULL.
 * - <b>Forbidden in catch-block!</b>
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_EXIT_IF_NULL(function) \
do { \
    if ((function) == NULL) \
    { \
        cpl_error_set_where(__func__); \
        kmo_error_is_set_where = __LINE__ ; \
        KMO_TRY_EXIT(); \
    } \
} while (0)

/*----------------------------------------------------------------------------*/
/**
 * @brief   If @a condition == 0, then
 *          the try-block is exited.
 * @hideinitializer
 *
 * - It is assumed, that a new error state is already set if @a condition is 0.
 * - <b>Forbidden in catch-block!</b>
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_EXIT_IF_NOT(condition) \
do { \
    if (!(condition)) \
    { \
        cpl_error_set_where(__func__); \
        kmo_error_is_set_where = __LINE__; \
        KMO_TRY_EXIT(); \
    } \
} while (0)

/*----------------------------------------------------------------------------*/
/**
 * @brief   The try-block is exited.
 * @hideinitializer
 *
 * - It is not necessary that a new error state is already set. In this case,
 *   the try-block is just exited.
 * - <b>Forbidden in catch-block!</b>
 */
/*----------------------------------------------------------------------------*/
#define KMO_TRY_EXIT(void) \
    goto _KMO_CATCH_LABEL_

/**
 * @brief  error handling macro
           (from fors-pipeline)
 * @param  condition   boolean expression to evaluate
 * @param  action      statement to execute if condition fails
 * @param  message     error message, or NULL
 */
#define KMO_TRY_ASSURE(condition, error, ...)               \
do if (!(condition)) {                                      \
    cpl_error_set_message(cpl_func,                         \
                          error,                            \
                          __VA_ARGS__);                     \
    kmo_error_is_set_where = __LINE__;                             \
    KMO_TRY_EXIT();                                         \
} while(0)

/*----------------------------------------------------------------------------*/
/**
 * @brief   Displays an error message
 *
 * @hideinitializer
 *
 * - <b>To be used in catch-block!</b>
 */
/*----------------------------------------------------------------------------*/
#define KMO_CATCH_MSG() \
do { \
    cpl_msg_error(cpl_func, \
                  "%s (Code %d) in %s", \
                  cpl_error_get_message(), \
                  cpl_error_get_code(), \
                  cpl_error_get_where()); \
} while (0)
/** @} Doxygen group end */

/*-----------------------------------------------------------------------------
    Declaration Block
 -----------------------------------------------------------------------------*/

#ifdef __cplusplus
extern "C" {
#endif

/*----------------------------------------------------------------------------*/

#ifdef __cplusplus
}   /* extern "C" */
#endif

#endif /* KMO_PRIV_ERROR_H */
