/*
 *  Avis Elvin client library for C.
 *
 *  Copyright (C) 2008 Matthew Phillips <avis@mattp.name>
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of version 3 of the GNU Lesser General
 *  Public License as published by the Free Software Foundation.
 *
 *  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, see <http://www.gnu.org/licenses/>.
 */
/** \file
 * Error handling.
 */
#ifndef AVIS_ERRORS_H_
#define AVIS_ERRORS_H_

#include <stdlib.h>

#include <avis/stdtypes.h>
#include <avis/defs.h>

/**
 * Error reporting information for the Avis client library. Functions
 * in the library that may fail will use a pointer to an
 * ElvinError instance to indicate, via an error code and message, if the
 * function or any sub function fails. As a convenience, functions which
 * would otherwise return void will often return true/false to indicate
 * error status.
 *
 * Note that the Elvin struct used for client connections contains an
 * embedded error context that takes the place of an explicit error
 * context parameter.
 *
 * Example:
 * <pre>
 * ElvinError error = ELVIN_EMPTY_ERROR;
 *
 * function_that_may_fail (arg1, arg2, ..., &error);
 *
 * if (elvin_error_occurred (&error))
 *   elvin_perror ("error", &error);
 *
 * elvin_error_free (&error);
 * </pre>
 *
 * @see elvin_error_set()
 * @see elvin_error_ok()
 * @see on_error_return_false()
 * @see on_error_return()
 */
typedef struct
{
  int    code;
  char * message;
} ElvinError;

#define ELVIN_ERROR_BASE       0
#define ELVIN_ERRNO_BASE       10000
#define ELVIN_HOST_ERROR_BASE  20000

#define ELVIN_ERROR_NONE                0
#define ELVIN_ERROR_INTERNAL            (ELVIN_ERROR_BASE + 1)
#define ELVIN_ERROR_PROTOCOL            (ELVIN_ERROR_BASE + 2)
#define ELVIN_ERROR_CONNECTION_CLOSED   (ELVIN_ERROR_BASE + 3)
#define ELVIN_ERROR_INVALID_URI         (ELVIN_ERROR_BASE + 4)
#define ELVIN_ERROR_SYNTAX              (ELVIN_ERROR_BASE + 5)
#define ELVIN_ERROR_TRIVIAL_EXPRESSION  (ELVIN_ERROR_BASE + 6)
#define ELVIN_ERROR_NACK                (ELVIN_ERROR_BASE + 7)
#define ELVIN_ERROR_USAGE               (ELVIN_ERROR_BASE + 8)
#define ELVIN_ERROR_TIMEOUT             (ELVIN_ERROR_BASE + 9)
#define ELVIN_ERROR_ROUTER_FAILURE      (ELVIN_ERROR_BASE + 10)

AVIS_PUBLIC
void *do_avis_emalloc (size_t size, const char *file, int line);

AVIS_PUBLIC
char *do_avis_estrdup (const char *str, const char *file, int line);

#define avis_emalloc(size) do_avis_emalloc ((size), __FILE__, __LINE__)

#define avis_estrdup(str) do_avis_estrdup ((str), __FILE__, __LINE__)

/**
 * An empty error instance. This can be assigned to an error instance to
 * initialise it on declaration rather than using elvin_error_init() (which is
 * the other method). Regardless of initialisation method, elvin_error_free()
 * should be used to release any resources allocated before disposing.
 */
#define ELVIN_EMPTY_ERROR {ELVIN_ERROR_NONE, NULL}

/**
 * Free any resources allocated to an error instance and reset the
 * error code. The error instance may be reused after this call.
 *
 * @see elvin_error_reset()
 */
AVIS_PUBLIC
void elvin_error_free (ElvinError *error);

/**
 * Initialise an uninitialised error instance.
 *
 * @see ELVIN_EMPTY_ERROR
 * @see elvin_error_reset()
 */
AVIS_PUBLIC
void elvin_error_init (ElvinError *error);

/**
 * Free any resources and reset the error info back to OK state.
 */
#define elvin_error_reset(error) (elvin_error_free (error))

/**
 * Macro statement to return false if an error is set in the "error"
 * variable inside the current scope.
 *
 * @param stat The statement to execute before the test.
 *
 * See also on_error_return().
 */
#define on_error_return_false(stat) on_error_return (stat, false)

/**
 * Macro statement to return a given value if an error is set in the
 * "error" variable inside the current scope.
 *
 * @param stat The statement to execute before the test.
 * @param retval The value to return on error.
 *
 * See also on_error_return().
 */
#define on_error_return(stat, retval) \
  {stat; if (elvin_error_occurred (error)) return (retval);}

/**
 * Convert the h_errno error code generated by gethostbyname () etc to
 * an error code that can be used with elvin_error_set().
 */
#define host_to_elvin_error(code) (ELVIN_HOST_ERROR_BASE + (code))

/**
 * Translate a system errno error code into a code that can be used with
 * elvin_error_set().
 */
#define errno_to_elvin_error(code) (ELVIN_ERRNO_BASE + (code))

/**
 * Like perror () but taking error info from an ElvinError instance.
 */
AVIS_PUBLIC
void elvin_perror (const char *tag, ElvinError *error);

/**
 * Load an error status from the system's "errno" error variable and
 * strerror () function.
 *
 * @param error The error to affect.
 *
 * @see elvin_error_set()
 */
AVIS_PUBLIC
bool elvin_error_from_errno (ElvinError *error);

/**
 * Signal an error has occurred. If an error is already set, this has
 * no effect (see elvin_error_reset() if you want to override any
 * existing error status).
 *
 * @param error The error to target.
 * @param code The error code. One of the ELVIN_ERROR_* defines or
 * host_to_elvin_error().
 * @param message The message, possibly including printf-style format
 * placeholders.
 * @param ... The rest of the arguments if message contains format
 * placeholders.
 *
 * @return Returns false as a convenience so that this can be used in
 * return statements indicating an error.
 *
 * @see elvin_error_from_errno()
 * @see elvin_error_free()
 */
AVIS_PUBLIC
bool elvin_error_set (ElvinError *error, int code, const char *message, ...);

/**
 * True if no error has occurred.
 */
#define elvin_error_ok(error) ((error)->code == ELVIN_ERROR_NONE)

/**
 * True if an error has occurred.
 */
#define elvin_error_occurred(error) (!elvin_error_ok (error))

#endif /*AVIS_ERRORS_H_*/
