/** @file io.c
 * MAT I/O Functions
 * @ingroup MAT
 */
/*
 * Copyright (C) 2005-2006   Christopher C. Hulbert
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

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

#if !defined(HAVE_VA_COPY) && defined(HAVE___VA_COPY)
#    define va_copy(d,s) __va_copy(d,s)
#elif !defined(HAVE_VA_COPY)
#    define va_copy(d,s) memcpy(&(d),&(s),sizeof(va_list))
#endif
#ifndef HAVE_VSNPRINTF
#    define vsnprintf mat_vsnprintf
#    ifdef  __cplusplus
         extern "C" int vsnprintf(char *,size_t,const char *,va_list);
#    else
         extern int vsnprintf(char *,size_t,const char *,va_list);
#    endif
#endif
#ifndef HAVE_SNPRINTF
#    define snprintf mat_snprintf
#    ifdef  __cplusplus
         extern "C" int snprintf(char *str,size_t size,const char *format,...);
#    else
         extern int snprintf(char *str,size_t size,const char *format,...);
#    endif
#endif
#ifndef HAVE_VASPRINTF
#    define vasprintf mat_vasprintf
#endif
#ifndef HAVE_ASPRINTF
#    define asprintf mat_asprintf
#endif

/** @cond 0 */
#define LOG_LEVEL_ERROR    1
#define LOG_LEVEL_CRITICAL 1 << 1
#define LOG_LEVEL_WARNING  1 << 2
#define LOG_LEVEL_MESSAGE  1 << 3
#define LOG_LEVEL_DEBUG    1 << 4
/** @endif */

static void (*logfunc)(int log_level, char *message ) = NULL;
static char *progname = NULL;

/** @brief Allocates and prints to a new string
 *
 * @param format format string
 * @param ap variable argument list
 * @return Newly allocated string with format printed to it
 */
char *
strdup_vprintf(const char* format, va_list ap)
{
  va_list ap2;
  int size;
  char* buffer;

  va_copy(ap2, ap);
  size = vsnprintf(NULL, 0, format, ap2)+1;
  va_end(ap2);

  buffer = malloc(size+1);
  if ( !buffer )
      return NULL;

  vsnprintf(buffer, size, format, ap);
  return buffer;
}

char *
strdup_printf(const char* format, ...)
{
  char* buffer;
  va_list ap;
  va_start(ap, format);
  buffer = strdup_vprintf(format, ap);
  va_end(ap);
  return buffer;
}

static void
matio_error_func( int log_level, char *message )
{

    if ( progname ) {
        if ( log_level & LOG_LEVEL_CRITICAL) {
            fprintf(stderr,"-E- %s: %s\n", progname, message);
            fflush(stderr);
        } else if ( log_level & LOG_LEVEL_ERROR ) {
            fprintf(stderr,"-E- %s: %s\n", progname, message);
            fflush(stderr);
            abort();
        } else if ( log_level & LOG_LEVEL_WARNING ) {
            fprintf(stderr,"-W- %s: %s\n", progname, message);
            fflush(stderr);
        } else if ( log_level & LOG_LEVEL_DEBUG ) {
            fprintf(stderr,"-D- %s: %s\n", progname, message);
            fflush(stderr);
        } else if ( log_level & LOG_LEVEL_MESSAGE ) {
            fprintf(stdout,"%s\n", message);
            fflush(stdout);
        }
    } else {
        if ( log_level & LOG_LEVEL_CRITICAL) {
            fprintf(stderr,"-E- : %s\n", message);
            fflush(stderr);
        } else if ( log_level & LOG_LEVEL_ERROR ) {
            fprintf(stderr,"-E- : %s\n", message);
            fflush(stderr);
            abort();
        } else if ( log_level & LOG_LEVEL_WARNING ) {
            fprintf(stderr,"-W- : %s\n", message);
            fflush(stderr);
        } else if ( log_level & LOG_LEVEL_DEBUG ) {
            fprintf(stderr,"-D- : %s\n", message);
            fflush(stderr);
        } else if ( log_level & LOG_LEVEL_MESSAGE ) {
            fprintf(stdout,"%s\n", message);
            fflush(stdout);
        }
    }

}

static void
scats_log(int loglevel, const char *format, va_list ap)
{
    char* buffer;

    if ( !logfunc ) return;
    buffer = strdup_vprintf(format, ap);
    (*logfunc)(loglevel,buffer);
    free(buffer);
    return;
}


/** @var debug
 *  @brief holds the verbose level set in @ref SetVerbose
 *  This variable is used to determine if information should be printed to
 *  the screen
 *  @ingroup libscatsio
 */
static int debug = 0;

/** @var verbose
 *  @brief holds the verbose level set in @ref SetVerbose
 *  This variable is used to determine if information should be printed to
 *  the screen
 *  @ingroup libscatsio
 */
static int verbose = 0;
/** @var silent
 *  @brief holds the silent level set in @ref SetVerbose
 *  If set, all output which is not an error is not displayed regardless
 *  of verbose level
 *  @ingroup libscatsio
 */
static int silent = 0;
/** @brief Sets verbose parameters
 *
 *  Sets the verbose level and silent level.  These values are used by
 *  programs to determine what information should be printed to the screen
 *  @ingroup libscatsio
 *  @param verb sets logging verbosity level
 *  @param s sets logging silent level
 */
int Mat_SetVerbose( int verb, int s )
{

    verbose = verb;
    silent  = s;

    return 0;
}

/** @brief Sets verbose parameters
 *
 *  Sets the verbose level and silent level.  These values are used by
 *  programs to determine what information should be printed to the screen
 *  @ingroup libscatsio
 *  @param verb sets logging verbosity level
 *  @param s sets logging silent level
 */
int Mat_SetDebug( int d )
{
    debug = d;
    return 0;
}

/** @brief Log a message unless silent
 *
 * Logs the message unless the silent option is set (See @ref SetVerbose).
 * To log a message based on the verbose level, use @ref Mat_VerbMessage
 * @ingroup libscatsio
 * @param format message format
 */
int Mat_Message( const char *format, ... )
{
    va_list ap;

    if ( silent ) return 0;
    if ( !logfunc ) return 0;

    va_start(ap, format );
    scats_log(LOG_LEVEL_MESSAGE, format, ap );
    va_end(ap);
    return 0;
}

/** @brief Log a message based on verbose level
 *
 *  If @e level is less than or equal to the set verbose level, the message
 *  is printed.  If the level is higher than the set verbose level nothing
 *  is displayed.
 *  @ingroup libscatsio
 *  @param level verbose level
 *  @param format message format
 */
int Mat_DebugMessage( int level, const char *format, ... )
{
    va_list ap;

    if ( silent ) return 0;
    if ( level > debug ) return 0;

    va_start(ap, format );
    scats_log(LOG_LEVEL_DEBUG, format, ap );
    va_end(ap);
    return 0;
}

/** @brief Log a message based on verbose level
 *
 *  If @e level is less than or equal to the set verbose level, the message
 *  is printed.  If the level is higher than the set verbose level nothing
 *  is displayed.
 *  @ingroup libscatsio
 *  @param level verbose level
 *  @param format message format
 */
int Mat_VerbMessage( int level, const char *format, ... )
{
    va_list ap;

    if ( silent ) return 0;
    if ( level > verbose ) return 0;

    va_start(ap, format );
    scats_log(LOG_LEVEL_MESSAGE, format, ap );
    va_end(ap);
    return 0;
}

/** @brief Logs a Critical message and returns to the user
 *
 * Logs a Critical message and returns to the user.  If the program should
 * stop running, use @ref Mat_Error
 * @ingroup libscatsio
 * @param format format string identical to printf format
 * @param ... arguments to the format string
 */
void Mat_Critical( const char *format, ... )
{
    va_list ap;

    va_start(ap, format );
    scats_log(LOG_LEVEL_CRITICAL, format, ap );
    va_end(ap);
}

/** @brief Logs a Critical message and aborts the program
 *
 * Logs an Error message and aborts
 * @ingroup libscatsio
 * @param format format string identical to printf format
 * @param ... arguments to the format string
 */
void Mat_Error( const char *format, ... )
{
    va_list ap;

    va_start(ap, format );
    scats_log( LOG_LEVEL_ERROR, format, ap );
    va_end(ap);
}

/** @brief Prints a helpstring to stdout and exits with status 1
 *
 * Prints the array of strings to stdout and exits with status 1.  The array
 * of strings should have NULL as its last element
 * @code
 * char *helpstr[] = {"My Help string line1","My help string line 2",NULL};
 * Mat_Help(helpstr);
 * @endcode
 * @ingroup libscatsio
 * @param helpstr array of strings with NULL as its last element
 */
void Mat_Help( const char *helpstr[] )
{
    int i;
    for (i = 0; helpstr[i] != NULL; i++)
        printf("%s\n",helpstr[i]);
    exit(EXIT_SUCCESS);
}

/** @brief Closes the logging system
 *
 * @retval 1
 */
int
Mat_LogClose( )
{
    logfunc = NULL;
    return 1;
}

/** @brief Intializes the logging system
 *
 * @ingroup libscatsio
 * @param prog_name Name of the program initializing the logging functions
 * @return 0 on success
 */
int
Mat_LogInit( char *prog_name )
{
    logfunc = &matio_error_func;

    verbose = 0;
    silent  = 0;

    return 0;
}

/** @brief Intializes the logging system
 *
 * @ingroup libscatsio
 * @param prog_name Name of the program initializing the logging functions
 * @param log_func pointer to the function to do the logging
 * @return 0 on success
 */
int
Mat_LogInitFunc(char *prog_name,void (*log_func)(int log_level,char *message))
{
    logfunc = log_func;
    progname = prog_name;

    verbose = 0;
    silent  = 0;
    return 0;
}

/** @brief Prints a warning message to stdout
 *
 * Logs a warning message then returns
 * @ingroup libscatsio
 * @param format format string identical to printf format
 * @param ... arguments to the format string
 */
void Mat_Warning( const char *format, ... )
{
    va_list ap;

    va_start(ap, format );
    scats_log(LOG_LEVEL_WARNING, format, ap );
    va_end(ap);
}

size_t
Mat_SizeOf(int data_type)
{
    switch (data_type) {
        case MAT_T_DOUBLE:
            return sizeof(double);
        case MAT_T_SINGLE:
            return sizeof(float);
#ifdef HAVE_MAT_INT64_T
        case MAT_T_INT64:
            return sizeof(mat_int64_t);
#endif
#ifdef HAVE_MAT_INT64_T
        case MAT_T_UINT64:
            return sizeof(mat_uint64_t);
#endif
        case MAT_T_INT32:
            return sizeof(mat_int32_t);
        case MAT_T_UINT32:
            return sizeof(mat_uint32_t);
        case MAT_T_INT16:
            return sizeof(mat_int16_t);
        case MAT_T_UINT16:
            return sizeof(mat_uint16_t);
        case MAT_T_INT8:
            return sizeof(mat_int8_t);
        case MAT_T_UINT8:
            return sizeof(mat_uint8_t);
        default:
            return 0;
    }
}
