/* foundry-debug.h.in
 *
 * Copyright 2025 Christian Hergert <chergert@redhat.com>
 *
 * 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 General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#pragma once

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

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

#include "foundry-version-macros.h"

G_BEGIN_DECLS

/**
 * SECTION:foundry-debug
 * @title: Debug logging and tracing
 * @short_description: tracing and debug facilities for Builder and plugins
 *
 * The debug macros such as %FOUNDRY_ENTRY, %FOUNDRY_EXIT, and %FOUNDRY_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.
 */

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

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

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

/**
 * FOUNDRY_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.
 */

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

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

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

/**
 * FOUNDRY_LOG_LEVEL_TRACE: (skip)
 */
#ifndef FOUNDRY_LOG_LEVEL_TRACE
# define FOUNDRY_LOG_LEVEL_TRACE G_LOG_LEVEL_DEBUG
#endif

FOUNDRY_AVAILABLE_IN_ALL
void foundry_trace_function (const gchar *strfunc,
                             gint64       begin_time_usec,
                             gint64       end_time_usec);

#ifdef FOUNDRY_ENABLE_TRACE
# define FOUNDRY_TRACE_MSG(fmt, ...)                                     \
   g_log(G_LOG_DOMAIN, FOUNDRY_LOG_LEVEL_TRACE, "  MSG: %s():%d: " fmt,  \
         G_STRFUNC, __LINE__, ##__VA_ARGS__)
# define FOUNDRY_PROBE                                                   \
   g_log(G_LOG_DOMAIN, FOUNDRY_LOG_LEVEL_TRACE, "PROBE: %s():%d",        \
         G_STRFUNC, __LINE__)
# define FOUNDRY_TODO(_msg)                                              \
   g_log(G_LOG_DOMAIN, FOUNDRY_LOG_LEVEL_TRACE, " TODO: %s():%d: %s",    \
         G_STRFUNC, __LINE__, _msg)
# define FOUNDRY_ENTRY                                                   \
   gint64 __trace_begin_time = g_get_monotonic_time ();                  \
   g_log(G_LOG_DOMAIN, FOUNDRY_LOG_LEVEL_TRACE, "ENTRY: %s():%d",        \
           G_STRFUNC, __LINE__)
# define FOUNDRY_EXIT                                                    \
   G_STMT_START {                                                        \
      foundry_trace_function (G_STRFUNC,                                 \
                              __trace_begin_time,                        \
                              g_get_monotonic_time ());                  \
      g_log(G_LOG_DOMAIN, FOUNDRY_LOG_LEVEL_TRACE, " EXIT: %s():%d",     \
            G_STRFUNC, __LINE__);                                        \
      return;                                                            \
   } G_STMT_END
# define FOUNDRY_GOTO(_l)                                                \
   G_STMT_START {                                                        \
      g_log(G_LOG_DOMAIN, FOUNDRY_LOG_LEVEL_TRACE,                       \
            " GOTO: %s():%d ("#_l")",                                    \
            G_STRFUNC, __LINE__);                                        \
      goto _l;                                                           \
   } G_STMT_END
# define FOUNDRY_RETURN(_r)                                              \
   G_STMT_START {                                                        \
      foundry_trace_function (G_STRFUNC,                                 \
                              __trace_begin_time,                        \
                              g_get_monotonic_time ());                  \
      g_log(G_LOG_DOMAIN, FOUNDRY_LOG_LEVEL_TRACE, " EXIT: %s():%d ",    \
            G_STRFUNC, __LINE__);                                        \
      return _r;                                                         \
   } G_STMT_END
# define FOUNDRY_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, FOUNDRY_LOG_LEVEL_TRACE, "TRACE: [%-2d]: %s",  \
            _i, symnames[_i]);                                           \
    }                                                                    \
    free (symnames);                                                     \
  } G_STMT_END
#define FOUNDRY_DUMP_BYTES(_n, _b, _l)                                   \
  G_STMT_START {                                                         \
    GString *str, *astr;                                                 \
    gsize _i;                                                            \
    g_log(G_LOG_DOMAIN, FOUNDRY_LOG_LEVEL_TRACE,                         \
          "         %s = %p [%d]", #_n, _b, (gint)_l);                   \
    str = g_string_sized_new (80);                                       \
    astr = g_string_sized_new (16);                                      \
    for (_i = 0; _i < _l; _i++)                                          \
      {                                                                  \
        if ((_i % 16) == 0)                                              \
          g_string_append_printf (str, "%06x: ", (guint)_i);             \
        g_string_append_printf (str, " %02x", _b[_i]);                   \
                                                                         \
        if (g_ascii_isprint(_b[_i]))                                     \
          g_string_append_printf (astr, " %c", _b[_i]);                  \
        else                                                             \
          g_string_append (astr, " .");                                  \
                                                                         \
        if ((_i % 16) == 15)                                             \
          {                                                              \
            g_log (G_LOG_DOMAIN, FOUNDRY_LOG_LEVEL_TRACE,                \
                   "%s  %s", str->str, astr->str);                       \
            str->str[0] = str->len = 0;                                  \
            astr->str[0] = astr->len = 0;                                \
          }                                                              \
        else if ((_i % 16) == 7)                                         \
          {                                                              \
            g_string_append_c (str, ' ');                                \
            g_string_append_c (astr, ' ');                               \
          }                                                              \
      }                                                                  \
                                                                         \
    if (_i != 16)                                                        \
      g_log (G_LOG_DOMAIN, FOUNDRY_LOG_LEVEL_TRACE,                      \
             "%-56s   %s", str->str, astr->str);                         \
                                                                         \
    g_string_free (str, TRUE);                                           \
    g_string_free (astr, TRUE);                                          \
  } G_STMT_END
#else
# define FOUNDRY_TODO(_msg)             G_STMT_START {            } G_STMT_END
# define FOUNDRY_PROBE                  G_STMT_START {            } G_STMT_END
# define FOUNDRY_TRACE_MSG(fmt, ...)    G_STMT_START {            } G_STMT_END
# define FOUNDRY_ENTRY                  G_STMT_START {            } G_STMT_END
# define FOUNDRY_GOTO(_l)               G_STMT_START { goto _l;   } G_STMT_END
# define FOUNDRY_EXIT                   G_STMT_START { return;    } G_STMT_END
# define FOUNDRY_RETURN(_r)             G_STMT_START { return _r; } G_STMT_END
# define FOUNDRY_BACKTRACE              G_STMT_START {            } G_STMT_END
# define FOUNDRY_DUMP_BYTES(_n, _b, _l) G_STMT_START {            } G_STMT_END
#endif

#define _FOUNDRY_BUG(Component, Description, File, Line, Func, ...)                     \
  G_STMT_START {                                                                        \
    g_printerr ("-----------------------------------------------------------------\n"); \
    g_printerr ("You've found a bug in Foundry 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/chergert/foundry/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 FOUNDRY_BUG(Component, Description, ...) \
  _FOUNDRY_BUG(Component, Description, __FILE__, __LINE__, G_STRFUNC, ##__VA_ARGS__)

#define FOUNDRY_IS_MAIN_THREAD() (foundry_thread_is_main(g_thread_self()))
FOUNDRY_AVAILABLE_IN_ALL
gboolean foundry_thread_is_main (GThread *thread);

G_END_DECLS
