/*
 * Copyright (C) 2015-2022 S[&]T, The Netherlands.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "harp-internal.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#define MAX_ERROR_INFO_LENGTH	4096

static int (*harp_warning_handler)(const char *, va_list ap) = NULL;
static char harp_error_message_buffer[MAX_ERROR_INFO_LENGTH + 1];

/** \defgroup harp_error HARP Error
 * With a few exceptions almost all HARP functions return an integer that indicate whether the function was able to
 * perform its operations successfully. The return value will be 0 on success and -1 otherwise. In case you get a -1
 * you can look at the global variable #harp_errno for a precise error code. Each error code and its meaning is
 * described in this section. You will also be able to retrieve a character string with an error description via
 * the harp_errno_to_string() function. This function will return either the default error message for the error
 * code, or a custom error message. A custom error message will only be returned if the error code you pass to
 * harp_errno_to_string() is equal to the last error that occurred and if this last error was set with a custom error
 * message. The HARP error state can be set with the harp_set_error() function.<br>
 */

/** \addtogroup harp_error
 * @{
 */

/** \name Error values
 * \ingroup harp_error
 * @{
 */

/** \def HARP_SUCCESS
 * Success (no error).
 */
/** \def HARP_ERROR_OUT_OF_MEMORY
 * Out of memory.
 */
/** \def HARP_ERROR_HDF4
 * An error occurred in the HDF4 library.
 */
/** \def HARP_ERROR_NO_HDF4_SUPPORT
 * No HDF4 support built into HARP.
 */
/** \def HARP_ERROR_HDF5
 * An error occurred in the HDF5 library.
 */
/** \def HARP_ERROR_NO_HDF5_SUPPORT
 * No HDF5 support built into HARP.
 */
/** \def HARP_ERROR_NETCDF
 * An error occurred in the netCDF library.
 */
/** \def HARP_ERROR_CODA
 * An error occurred in the CODA library.
 */

/** \def HARP_ERROR_FILE_NOT_FOUND
 * File not found.
 */
/** \def HARP_ERROR_FILE_OPEN
 * Could not open file.
 */
/** \def HARP_ERROR_FILE_CLOSE
 * Could not close file.
 */
/** \def HARP_ERROR_FILE_READ
 * Could not read data from file.
 */
/** \def HARP_ERROR_FILE_WRITE
 * Could not write data to file.
 */

/** \def HARP_ERROR_INVALID_ARGUMENT
 * Invalid argument.
 */
/** \def HARP_ERROR_INVALID_INDEX
 * Invalid index argument.
 */
/** \def HARP_ERROR_INVALID_NAME
 * Invalid name argument.
 */
/** \def HARP_ERROR_INVALID_FORMAT
 * Invalid format in argument.
 */
/** \def HARP_ERROR_INVALID_DATETIME
 * Invalid date/time argument.
 */
/** \def HARP_ERROR_INVALID_TYPE
 * Invalid type.
 */
/** \def HARP_ERROR_ARRAY_NUM_DIMS_MISMATCH
 * Incorrect number of dimensions argument.
 */
/** \def HARP_ERROR_ARRAY_OUT_OF_BOUNDS
 * Array index out of bounds.
 */
/** \def HARP_ERROR_VARIABLE_NOT_FOUND
 * Variable not found.
 */

/** \def HARP_ERROR_UNIT_CONVERSION
 * An error occurred in the unit conversion.
 */

/** \def HARP_ERROR_OPERATION
 * There was an error detected in the product operations.
 */
/** \def HARP_ERROR_OPERATION_SYNTAX
 * There is a syntax error in the string defining the product operations.
 */

/** \def HARP_ERROR_IMPORT
 * An error occurred during product import.
 */

/** \def HARP_ERROR_EXPORT
 * An error occurred during product export.
 */

/** \def HARP_ERROR_INGESTION
 * There was an error in the ingestion of a data product.
 */
/** \def HARP_ERROR_INGESTION_OPTION_SYNTAX
 * There was a syntax error in the ingestion option.
 */
/** \def HARP_ERROR_INVALID_INGESTION_OPTION
 * The ingestion option is not valid for this ingestion.
 */
/** \def HARP_ERROR_INVALID_INGESTION_OPTION_VALUE
 * The ingestion option has value that is not valid for this option.
 */

/** \def HARP_ERROR_UNSUPPORTED_PRODUCT
 * The data product is not supported by the import or ingestion module.
 */

/** \def HARP_ERROR_NO_DATA
 * The operation resulted in an 'empty' product.
 */

/** @} */

/** Variable that contains the error type.
 * If no error has occurred the variable contains #HARP_SUCCESS (0).
 * \hideinitializer
 */
LIBHARP_API int harp_errno = HARP_SUCCESS;

/** @} */

static void add_error_message_vargs(const char *message, va_list ap)
{
    size_t current_length;

    if (message == NULL)
    {
        return;
    }

    current_length = strlen(harp_error_message_buffer);
    if (current_length >= MAX_ERROR_INFO_LENGTH)
    {
        return;
    }
    vsnprintf(&harp_error_message_buffer[current_length], MAX_ERROR_INFO_LENGTH - current_length, message, ap);
    harp_error_message_buffer[MAX_ERROR_INFO_LENGTH] = '\0';
}

static int add_error_message(const char *message, ...)
{
    va_list ap;

    va_start(ap, message);
    add_error_message_vargs(message, ap);
    va_end(ap);

    return 0;
}

static void set_error_message_vargs(const char *message, va_list ap)
{
    if (message == NULL)
    {
        harp_error_message_buffer[0] = '\0';
    }
    else
    {
        vsnprintf(harp_error_message_buffer, MAX_ERROR_INFO_LENGTH, message, ap);
        harp_error_message_buffer[MAX_ERROR_INFO_LENGTH] = '\0';
    }
}

void harp_add_coda_cursor_path_to_error_message(const coda_cursor *cursor)
{
    harp_add_error_message(" at '");
    coda_cursor_print_path(cursor, add_error_message);
    harp_add_error_message("'");
}

/** \addtogroup harp_error
 * @{
 */

/** Extend the current error message with additional information.
 * \param message Error message using printf() format.
 */
LIBHARP_API void harp_add_error_message(const char *message, ...)
{
    va_list ap;

    va_start(ap, message);
    add_error_message_vargs(message, ap);
    va_end(ap);
}

/** Set the error value and optionally set a custom error message.
 * If \a message is NULL then the default error message for the error number will be used.
 * \param err Value of #harp_errno.
 * \param message Optional error message using printf() format.
 */
LIBHARP_API void harp_set_error(int err, const char *message, ...)
{
    va_list ap;

    harp_errno = err;

    va_start(ap, message);
    set_error_message_vargs(message, ap);
    va_end(ap);

#ifdef HAVE_HDF4
    if (err == HARP_ERROR_HDF4 && message == NULL)
    {
        harp_hdf4_add_error_message();
    }
#endif
#ifdef HAVE_HDF5
    if (err == HARP_ERROR_HDF5 && message == NULL)
    {
        harp_hdf5_add_error_message();
    }
#endif
    if (err == HARP_ERROR_CODA && message == NULL)
    {
        harp_add_error_message("%s", coda_errno_to_string(coda_errno));
    }
}

/** Returns a string with the description of the HARP error.
 * If \a err equals the current HARP error status then this function will return the error message that was last set
 * using harp_set_error(). If the error message argument to harp_set_error() was NULL or if \a err does not equal the
 * current HARP error status then the default error message for \a err will be returned.
 * \param err Value of #harp_errno.
 * \return String with a description of the HARP error.
 */
LIBHARP_API const char *harp_errno_to_string(int err)
{
    if (err == harp_errno && harp_error_message_buffer[0] != '\0')
    {
        /* return the custom error message for the current HARP error */
        return harp_error_message_buffer;
    }
    else
    {
        switch (err)
        {
            case HARP_SUCCESS:
                return "success (no error)";
            case HARP_ERROR_OUT_OF_MEMORY:
                return "out of memory";

            case HARP_ERROR_HDF4:
                return "HDF4 error";
            case HARP_ERROR_NO_HDF4_SUPPORT:
                return "HDF4 is not supported (this version of HARP was not built with HDF4 support)";
            case HARP_ERROR_HDF5:
                return "HDF5 error";
            case HARP_ERROR_NO_HDF5_SUPPORT:
                return "HDF5 is not supported (this version of HARP was not built with HDF5 support)";
            case HARP_ERROR_NETCDF:
                return "netCDF error";
            case HARP_ERROR_CODA:
                return "CODA error";

            case HARP_ERROR_FILE_NOT_FOUND:
                return "file not found";
            case HARP_ERROR_FILE_OPEN:
                return "error opening file";
            case HARP_ERROR_FILE_CLOSE:
                return "error closing file";
            case HARP_ERROR_FILE_READ:
                return "error reading file";
            case HARP_ERROR_FILE_WRITE:
                return "error writing file";

            case HARP_ERROR_INVALID_ARGUMENT:
                return "invalid argument";
            case HARP_ERROR_INVALID_INDEX:
                return "invalid index";
            case HARP_ERROR_INVALID_NAME:
                return "invalid name";
            case HARP_ERROR_INVALID_FORMAT:
                return "invalid format";
            case HARP_ERROR_INVALID_DATETIME:
                return "invalid date/time";
            case HARP_ERROR_INVALID_TYPE:
                return "invalid type";
            case HARP_ERROR_ARRAY_NUM_DIMS_MISMATCH:
                return "incorrect number of dimensions";
            case HARP_ERROR_ARRAY_OUT_OF_BOUNDS:
                return "array index out of bounds";
            case HARP_ERROR_VARIABLE_NOT_FOUND:
                return "variable not found";

            case HARP_ERROR_UNIT_CONVERSION:
                return "unit conversion error";

            case HARP_ERROR_OPERATION:
                return "product operations error";
            case HARP_ERROR_OPERATION_SYNTAX:
                return "syntax error in product operations string";

            case HARP_ERROR_IMPORT:
                return "import error";
            case HARP_ERROR_EXPORT:
                return "export error";

            case HARP_ERROR_INGESTION:
                return "ingestion error";
            case HARP_ERROR_INGESTION_OPTION_SYNTAX:
                return "syntax error in ingestion option";
            case HARP_ERROR_INVALID_INGESTION_OPTION:
                return "invalid ingestion option";
            case HARP_ERROR_INVALID_INGESTION_OPTION_VALUE:
                return "invalid ingestion option value";

            case HARP_ERROR_UNSUPPORTED_PRODUCT:
                return "unsupported product";

            case HARP_ERROR_NO_DATA:
                return "no data left after operation";

            default:
                if (err == harp_errno)
                {
                    return harp_error_message_buffer;
                }
                else
                {
                    return "";
                }
        }
    }
}

/** Report a warning message
 * The warning message will be passed on to the current warning handler that was set by harp_set_warning_handler().
 * If no warning handler was set, then this function will do nothing (and return the value 0).
 * The convention is for warning messages to start with a non-capital letter and not contain end-of-line characters.
 * This is similar to the error messages that can be set with harp_set_error().
 * Printing of line endings, if these are needed, should be performed by the warning handler.
 * \param message Warning message using printf() format.
 * \return Return code from the warning handler.
 */
LIBHARP_API int harp_report_warning(const char *message, ...)
{
    int result = 0;

    if (harp_warning_handler != NULL)
    {
        va_list ap;

        va_start(ap, message);
        result = harp_warning_handler(message, ap);
        va_end(ap);
    }

    return result;
}

/** Get a reference to the current handler for warning messages
 * If no warning handler was set, the NULL pointer will be returned.
 * \param print Pointer to the variable in which the reference to the vprintf compatible function will be stored
 * \return
 *   \arg \c  0, Success.
 *   \arg \c -1, Error occurred (check #harp_errno).
 */
LIBHARP_API int harp_get_warning_handler(int (**print)(const char *, va_list ap))
{
    *print = harp_warning_handler;
    return 0;
}

/** Set handler for warning messages
 * The \a print function parameter should be a function that resembles vprintf().
 * The most common case is to provide a function that prints a 'WARNING' prefix, prints the message, and adds
 * a newline. For example:
 * \code{.c}
 * static int print_warning(const char *message, va_list ap)
 * {
 *    int result;
 *    printf("WARNING: ");
 *    result = vprintf(message, ap);
 *    printf("\n");
 *    return result;
 * }
 * harp_set_warning_handler(print_warning);
 * \endcode
 * The handler function will get called whenever harp_report_warning() is called (several functions inside the HARP
 * library may call harp_report_warning() to report on certain warning conditions).
 * The warning handler can be set before a call to harp_init() is made.
 * \param print Reference to a vprintf compatible function.
 * \return
 *   \arg \c  0, Success.
 *   \arg \c -1, Error occurred (check #harp_errno).
 */
LIBHARP_API int harp_set_warning_handler(int (*print)(const char *, va_list ap))
{
    harp_warning_handler = print;
    return 0;
}


/** @} */
