/*
 * Copyright 1999-2006 University of Chicago
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file globus_debug.h
 * @brief Debugging Routines
 */

#ifndef GLOBUS_DEBUG_H
#define GLOBUS_DEBUG_H

#include "globus_common_include.h"
#include "globus_time.h"

#ifdef __cplusplus
extern "C" {
#endif

#ifdef BUILD_DEBUG

typedef struct
{
    unsigned                            levels;
    unsigned                            timestamp_levels;
    FILE *                              file;
    globus_bool_t                       thread_ids;
    globus_bool_t                       using_file;
} globus_debug_handle_t;

void
globus_debug_init(
    const char *                        env_name,
    const char *                        level_names,
    globus_debug_handle_t *             handle);

#define GlobusDebugThreadId() globus_thread_self()

/* call in same file as module_activate func (before (de)activate funcs) */
#define GlobusDebugDefine(module_name)                                      \
    extern globus_debug_handle_t globus_i_##module_name##_debug_handle;     \
    void globus_i_##module_name##_debug_printf(const char * fmt, ...)       \
    {                                                                       \
        va_list ap;                                                         \
	                                                                    \
        if(!globus_i_##module_name##_debug_handle.file)                     \
            return;                                                         \
                                                                            \
        va_start(ap, fmt);                                                  \
        if(globus_i_##module_name##_debug_handle.thread_ids)                \
        {                                                                   \
            char buf[4096]; /* XXX better not use a fmt bigger than this */ \
            sprintf(                                                        \
                buf, "%lu::%s", (unsigned long) globus_thread_self().dummy, fmt);\
            vfprintf(globus_i_##module_name##_debug_handle.file, buf, ap);  \
        }                                                                   \
        else                                                                \
        {                                                                   \
            vfprintf(globus_i_##module_name##_debug_handle.file, fmt, ap);  \
        }                                                                   \
        va_end(ap);                                                         \
    }                                                                       \
    void globus_i_##module_name##_debug_time_printf(const char * fmt, ...)  \
    {                                                                       \
        va_list ap;                                                         \
        char buf[4096]; /* XXX better not use a fmt bigger than this */     \
        globus_abstime_t current_time;                                      \
	                                                                    \
        if(!globus_i_##module_name##_debug_handle.file)                     \
            return;                                                         \
                                                                            \
        GlobusTimeAbstimeGetCurrent(current_time);                          \
        va_start(ap, fmt);                                                  \
        if(globus_i_##module_name##_debug_handle.thread_ids)                \
        {                                                                   \
            globus_thread_t __self = GlobusDebugThreadId();                 \
            sprintf(buf, "%lu:%lu.%.9lu::%s",                               \
                (unsigned long) __self.dummy,                               \
                (unsigned long) current_time.tv_sec,                        \
                (unsigned long) current_time.tv_nsec,                       \
                fmt);                                                       \
            vfprintf(globus_i_##module_name##_debug_handle.file, buf, ap);  \
        }                                                                   \
        else                                                                \
        {                                                                   \
            sprintf(buf, "%lu.%.9lu::%s",                                   \
                (unsigned long) current_time.tv_sec,                        \
                (unsigned long) current_time.tv_nsec,                       \
                fmt);                                                       \
            vfprintf(globus_i_##module_name##_debug_handle.file, buf, ap);  \
        }                                                                   \
        va_end(ap);                                                         \
    }                                                                       \
    void globus_i_##module_name##_debug_fwrite(                             \
        const void *ptr, size_t size, size_t  nmemb)                        \
    {                                                                       \
        if(globus_i_##module_name##_debug_handle.file)                      \
            fwrite(ptr, size, nmemb,                                        \
                globus_i_##module_name##_debug_handle.file);                \
    }                                                                       \
    globus_debug_handle_t globus_i_##module_name##_debug_handle

/* call this in a header file (if needed externally) */
#define GlobusDebugDeclare(module_name)                                     \
    extern void globus_i_##module_name##_debug_printf(const char *, ...);   \
    extern void globus_i_##module_name##_debug_time_printf(const char *, ...);\
    extern void globus_i_##module_name##_debug_fwrite(                      \
        const void *ptr, size_t size, size_t nmemb);                        \
    extern globus_debug_handle_t globus_i_##module_name##_debug_handle

/* call this in module activate func
 *
 * 'levels' is a space separated list of level names that can be used in env
 *    they will map to a 2^i value (so, list them in same order as value)
 *
 * will look in env for {module_name}_DEBUG whose value is:
 * <levels> [, [ [ # ] <file name>] [, <flags> [, <timestamp_levels>] ] ]
 * where <levels> can be a single numeric or '|' separated level names
 * <file name> is a debug output file... can be empty.  stderr by default
 *    if a '#' precedes the filename, the file will be overwritten on each run
 *    otherwise, the default is to append to the existing (if one exists)
 * <flags> 0 default (or any of the following to enable:
 *         1 show thread ids
 *         2 append pid to debug filename
 * <timestamp_levels> similar to <levels>. specifies which levels to print
 *   timestamps with.  default is none.
 * Also, users can use the ALL level in their env setting to turn on 
 * all levels or precede the list of levels with '^' to enable all levels
 * except those.
 */
#define GlobusDebugInit(module_name, levels)                                \
    globus_debug_init(                                                      \
        #module_name "_DEBUG",                                              \
        #levels,                                                            \
        &globus_i_##module_name##_debug_handle)

/* call this in module deactivate func */
#define GlobusDebugDestroy(module_name)                                     \
    do                                                                      \
    {                                                                       \
        if(globus_i_##module_name##_debug_handle.using_file)                \
        {                                                                   \
            fclose(globus_i_##module_name##_debug_handle.file);             \
        }                                                                   \
        globus_i_##module_name##_debug_handle.file = GLOBUS_NULL;           \
    } while(0)

/* use this to print a message unconditionally (message must be enclosed in
 * parenthesis and contains a format and possibly var args
 */
#define GlobusDebugMyPrintf(module_name, message)                           \
    globus_i_##module_name##_debug_printf message
#define GlobusDebugMyTimePrintf(module_name, message)                       \
    globus_i_##module_name##_debug_time_printf message

#define GlobusDebugMyFwrite(module_name, buffer, size, count)               \
    globus_i_##module_name##_debug_fwrite((buffer), (size), (count))

#define GlobusDebugMyFile(module_name)                                      \
    (globus_i_##module_name##_debug_handle.file)
    
/* use this in an if() to debug enable blocks of code 
 * for example
 * 
 * if(GlobusDebugTrue(MY_MODULE, VERIFICATION))
 * {
 *    compute stats
 *    GlobusDebugMyPrintf(MY_MODULE, "Stats = %d\n", stats);
 * }
 */
#define GlobusDebugTrue(module_name, level)                                 \
    (globus_i_##module_name##_debug_handle.levels & (level))

#define GlobusDebugTimeTrue(module_name, level)                             \
    (globus_i_##module_name##_debug_handle.timestamp_levels & (level))

/* most likely wrap this with your own macro,
 * so you don't need to pass module_name all the time
 * 'message' needs to be wrapped with parens and contains a format and
 * possibly var args
 */
#define GlobusDebugPrintf(module_name, level, message)                      \
    do                                                                      \
    {                                                                       \
        if(GlobusDebugTrue(module_name, level))                             \
        {                                                                   \
            if(!GlobusDebugTimeTrue(module_name, level))                    \
            {                                                               \
                GlobusDebugMyPrintf(module_name, message);                  \
            }                                                               \
            else                                                            \
            {                                                               \
                GlobusDebugMyTimePrintf(module_name, message);              \
            }                                                               \
        }                                                                   \
    } while(0)

#define GlobusDebugFwrite(module_name, level, buffer, size, count)          \
    do                                                                      \
    {                                                                       \
        if(GlobusDebugTrue(module_name, level))                             \
        {                                                                   \
            GlobusDebugMyFwrite(module_name, buffer, size, count);          \
        }                                                                   \
    } while(0)

#else

#define GlobusDebugThreadId()                                   0
#define GlobusDebugDeclare(module_name)
#define GlobusDebugDefine(module_name)
#define GlobusDebugInit(module_name, levels)                    do {} while(0)
#define GlobusDebugDestroy(module_name)                         do {} while(0)
#define GlobusDebugPrintf(module_name, level, message)          do {} while(0)
#define GlobusDebugFwrite(module_name, level, buffer, size, count)          \
                                                                do {} while(0)
#define GlobusDebugMyPrintf(module_name, message)               do {} while(0)
#define GlobusDebugMyTimePrintf(module_name, message)           do {} while(0)
#define GlobusDebugMyFwrite(module_name, buffer, size, count)   do {} while(0)
#define GlobusDebugMyFile(module_name)                          (stderr)
#define GlobusDebugTrue(module_name, level)                     0
#define GlobusDebugTimeTrue(module_name, level)                 0

#endif

#ifdef __cplusplus
}
#endif

#endif /* GLOBUS_DEBUG_H */
