/* ide-debug.h.in
 *
 * Copyright (C) 2015 Christian Hergert <christian@hergert.me>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#ifndef IDE_DEBUG_H
#define IDE_DEBUG_H

#ifndef IDE_ENABLE_TRACE
# define IDE_ENABLE_TRACE 0
#endif
#if IDE_ENABLE_TRACE != 1
# undef IDE_ENABLE_TRACE
#endif

#ifdef IDE_ENABLE_TRACE
#include <execinfo.h>
#endif
#include <glib.h>

#include "ide-version-macros.h"

G_BEGIN_DECLS

/**
 * SECTION:ide-debug
 * @title: Debug logging and tracing
 * @short_description: tracing and debug facilities for Builder and plugins
 *
 * The debug macros such as %IDE_ENTRY, %IDE_EXIT, and %IDE_RETURN provide
 * helpers for tracing Builder and plugins at runtime.
 *
 * These tracing macros will compile out when Builder is configured for a
 * release build. Otherwise, running Builder with the "-vvvv" command line
 * argument will show tracing output.
 */

/**
 * IDE_ENTRY: (skip)
 *
 * Traces the entry into a function. Place this at the beginning of your
 * function above pre-condition checks.
 */

/**
 * IDE_EXIT: (skip)
 *
 * Traces the exit from a function. Use this instead of "return" to return
 * and log the function exiting.
 */

/**
 * IDE_RETURN: (skip)
 *
 * Similar to %IDE_EXIT but allows providing a return value.
 */

/**
 * IDE_GOTO: (skip)
 * @_l: the label to jump to
 *
 * Appends to the jump to label to the tracing log and then jumps
 * to the label @_l.
 */

/**
 * IDE_TODO: (skip)
 * @_msg: the message to append to the trace log
 *
 * Appends to the tracing log that unsupported code has been
 * reached.
 */

/**
 * IDE_PROBE: (skip)
 *
 * Appends to the tracing log that a line of code was reached.
 */

/**
 * IDE_TRACE_MSG: (skip)
 *
 * Similar to %IDE_PROBE but allows specifying a log message.
 */

/**
 * IDE_LOG_LEVEL_TRACE: (skip)
 */
#ifndef IDE_LOG_LEVEL_TRACE
# define IDE_LOG_LEVEL_TRACE ((GLogLevelFlags)(1 << G_LOG_LEVEL_USER_SHIFT))
#endif

#ifdef IDE_ENABLE_TRACE
_IDE_EXTERN
void ide_trace_function (const gchar *strfunc,
                         gint64       begin_time_usec,
                         gint64       end_time_usec);
# define IDE_TRACE_MSG(fmt, ...)                                         \
   g_log(G_LOG_DOMAIN, IDE_LOG_LEVEL_TRACE, "  MSG: %s():%d: " fmt,      \
         G_STRFUNC, __LINE__, ##__VA_ARGS__)
# define IDE_PROBE                                                       \
   g_log(G_LOG_DOMAIN, IDE_LOG_LEVEL_TRACE, "PROBE: %s():%d",            \
         G_STRFUNC, __LINE__)
# define IDE_TODO(_msg)                                                  \
   g_log(G_LOG_DOMAIN, IDE_LOG_LEVEL_TRACE, " TODO: %s():%d: %s",        \
         G_STRFUNC, __LINE__, _msg)
# define IDE_ENTRY                                                       \
   gint64 __trace_begin_time = g_get_monotonic_time ();                  \
   g_log(G_LOG_DOMAIN, IDE_LOG_LEVEL_TRACE, "ENTRY: %s():%d",            \
           G_STRFUNC, __LINE__)
# define IDE_EXIT                                                        \
   G_STMT_START {                                                        \
      ide_trace_function (G_STRFUNC,                                     \
                          __trace_begin_time,                            \
                          g_get_monotonic_time ());                      \
      g_log(G_LOG_DOMAIN, IDE_LOG_LEVEL_TRACE, " EXIT: %s():%d",         \
            G_STRFUNC, __LINE__);                                        \
      return;                                                            \
   } G_STMT_END
# define IDE_GOTO(_l)                                                    \
   G_STMT_START {                                                        \
      g_log(G_LOG_DOMAIN, IDE_LOG_LEVEL_TRACE, " GOTO: %s():%d ("#_l")", \
            G_STRFUNC, __LINE__);                                        \
      goto _l;                                                           \
   } G_STMT_END
# define IDE_RETURN(_r)                                                  \
   G_STMT_START {                                                        \
      ide_trace_function (G_STRFUNC,                                     \
                          __trace_begin_time,                            \
                          g_get_monotonic_time ());                      \
      g_log(G_LOG_DOMAIN, IDE_LOG_LEVEL_TRACE, " EXIT: %s():%d ",        \
            G_STRFUNC, __LINE__);                                        \
      return _r;                                                         \
   } G_STMT_END
# define IDE_BACKTRACE                                                   \
  G_STMT_START {                                                         \
    gpointer btbuf[64];                                                  \
    int btbuflen = backtrace (btbuf, G_N_ELEMENTS (btbuf));              \
    char **symnames = backtrace_symbols (btbuf, btbuflen);               \
    for (guint _i = 0; _i < btbuflen; _i++) {                            \
      g_log(G_LOG_DOMAIN, IDE_LOG_LEVEL_TRACE, "TRACE: [%-2d]: %s",      \
            _i, symnames[_i]);                                           \
    }                                                                    \
    free (symnames);                                                     \
  } G_STMT_END
#else
# define IDE_TODO(_msg)          G_STMT_START {            } G_STMT_END
# define IDE_PROBE               G_STMT_START {            } G_STMT_END
# define IDE_TRACE_MSG(fmt, ...) G_STMT_START {            } G_STMT_END
# define IDE_ENTRY               G_STMT_START {            } G_STMT_END
# define IDE_GOTO(_l)            G_STMT_START { goto _l;   } G_STMT_END
# define IDE_EXIT                G_STMT_START { return;    } G_STMT_END
# define IDE_RETURN(_r)          G_STMT_START { return _r; } G_STMT_END
# define IDE_BACKTRACE           G_STMT_START {            } G_STMT_END
#endif

#define _IDE_BUG(Component, Description, File, Line, Func, ...)                         \
  G_STMT_START {                                                                        \
    g_printerr ("-----------------------------------------------------------------\n"); \
    g_printerr ("You've found a bug in Builder or one of its dependent libraries.\n");  \
    g_printerr ("Please help us help you by filing a bug report at:\n");                \
    g_printerr ("\n");                                                                  \
    g_printerr ("https://gitlab.gnome.org/GNOME/gnome-builder/issues&component=%s\n", Component);                           \
    g_printerr ("\n");                                                                  \
    g_printerr ("%s:%d in function %s()\n", File, Line, Func);                          \
    g_printerr ("\n");                                                                  \
    g_printerr (Description"\n", ##__VA_ARGS__);                                        \
    g_printerr ("-----------------------------------------------------------------\n"); \
  } G_STMT_END
#define IDE_BUG(Component, Description, ...) \
  _IDE_BUG(Component, Description, __FILE__, __LINE__, G_STRFUNC, ##__VA_ARGS__)

G_END_DECLS

#endif /* IDE_DEBUG_H */
