/* vmsglib.h - GLib substitute for the OpenVMS build of CSBwin.
 * ===========================================================================
 * OpenVMS 8.4 Alpha, HP C++ (CXX), SDL 1.2.
 *
 * WHY THIS FILE EXISTS
 *
 *   The SDL/POSIX flavour of CSBwin is selected by -D_LINUX, and _LINUX in
 *   this tree does NOT mean "Linux the kernel": it means "the SDL + POSIX
 *   platform layer" (CSBlinux.cpp + LinCSBUI.cpp + WinScreen.cpp).  That is
 *   exactly the layer the OpenVMS port wants, so the port keeps _LINUX
 *   defined rather than inventing a fourth platform.
 *
 *   The one thing _LINUX drags in that OpenVMS has no answer for is GLib:
 *   stdafx.h does #include <glib.h>, and CSBTypes.h builds the whole engine's
 *   fundamental typedefs (i8/ui8/i16/.../i64/ui64/bool32/HWND/HTIMER) out of
 *   GLib's gint16/guint32/... names, plus it decides endianness from
 *   G_BYTE_ORDER.  There is no GLib on OpenVMS 8.4, and dragging one in for
 *   fifteen typedefs, six varargs printf wrappers and two pointer casts would
 *   be absurd.  So stdafx.h includes THIS file instead when __VMS is defined.
 *
 *   The full list of what the compiled sources actually need from GLib is
 *   short, and it is all here:
 *
 *     types      gint16 guint16 gint32 guint32 gint64 guint64
 *                gboolean gchar gpointer            (CSBTypes.h, LinCSBUI.cpp)
 *     endianness G_BYTE_ORDER, G_LITTLE_ENDIAN, G_BIG_ENDIAN   (CSBTypes.h)
 *     casts      GINT_TO_POINTER, GPOINTER_TO_INT   (CSBlinux.cpp, SDL timer
 *                                                     and user-event codes)
 *     logging    g_print g_warning g_critical g_error g_assert
 *                and the g_log SIGNATURE, because CSBlinux.cpp DEFINES g_log
 *                itself (it stubs it out to die()) and that definition has to
 *                agree with a declaration somewhere.
 *     booleans   TRUE / FALSE
 *
 *   Everything else GLib-ish in the tree (GString, g_string_*, g_queue_*,
 *   g_signal_connect, g_set_application_name) sits inside #ifdef USE_OLD_GTK,
 *   which this port does not define - there is no GTK 2 on OpenVMS, and the
 *   GTK menu bar was only ever an optional extra over the SDL window.  Those
 *   names are deliberately NOT provided here: if someone ever defines
 *   USE_OLD_GTK on VMS the build should fail loudly at the first g_string_new
 *   rather than half-work.
 *
 * ENDIANNESS
 *
 *   OpenVMS Alpha and OpenVMS I64 are both LITTLE-endian, which is what this
 *   engine wants: DUNGEON.DAT, HCSB.DAT, the saved games and the recorded
 *   playback files are all little-endian Atari-ST-derived byte images, and
 *   CSBTypes.h's LE16/LE32 become no-ops when _littleEndian is defined.  So
 *   G_BYTE_ORDER == G_LITTLE_ENDIAN below is not a guess, and it means the
 *   VMS build reads the same data files as the Windows and Linux builds
 *   byte-for-byte.  (OpenVMS VAX is little-endian too, but VAX is rejected by
 *   CONFIGURE.COM for want of 64-bit integers - see i64 in CSBTypes.h.)
 *
 * POINTER WIDTH - AND WHY THAT MAKES THIS PORT EASY
 *
 *   OpenVMS Alpha C/C++ defaults to 32-BIT pointers (/POINTER_SIZE=32) with
 *   32-bit int and long and 64-bit long long.  That is the same model the
 *   original Win32 build and the original 32-bit Linux build used, which
 *   matters a great deal here: this engine is a re-implementation of 68000
 *   code and is riddled with structures whose field offsets are commented with
 *   their Atari ST addresses (see STRUCT148 in utility.cpp, offsets 0/4/8/12,
 *   which only hold if a pointer is 4 bytes).  Do NOT build this with
 *   /POINTER_SIZE=64.
 *
 * ===========================================================================
 */

#ifndef __VMSGLIB_H__
#define __VMSGLIB_H__

#ifndef __VMS
#error vmsglib.h is the OpenVMS GLib substitute and is only for __VMS builds
#endif

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <stddef.h>

/* --------------------------------------------------------------------------
 * intptr_t / uintptr_t
 *
 * The engine uses uintptr_t and intptr_t in interfaces that pass either a
 * pointer or a small integer through the same parameter - UI.h's
 * CSB_UI_MESSAGE::p3, UI_PushMessage(), Chaos.cpp's _CALL3/_CALL4 and the
 * atari_sprintf() varargs shim.  On Linux they arrive via <stdint.h>, pulled
 * in by glib.h.
 *
 * THE DEFAULT IS TO USE THE HEADER, AND THAT DEFAULT IS NOT A GUESS.  It was
 * measured on a real OpenVMS 8.4 Alpha system: <inttypes.h> there does declare
 * both types -
 *
 *     %CXX-E-BADTYPNAMRED, invalid redeclaration of type name "intptr_t"
 *               (declared at line 74 of "Text library
 *               SYS$COMMON:[SYSLIB]DECC$RTLDEF.TLB;1 module INTTYPES")
 *
 * which is what this file used to PROVOKE by typedef'ing them itself whenever
 * VMS_HAVE_INTPTR_T was absent.  The polarity was backwards: the fallback was
 * the default, so a system that HAD the types got a hard error, and it got it
 * on the very first file of the build.  Note that the clash is unconditional,
 * not merely likely: stdafx.h includes <SDL.h> before this file, SDL_stdinc.h
 * includes <inttypes.h> when HAVE_INTTYPES_H is set, and the OpenVMS
 * SDL_config.h sets it unconditionally.  So <inttypes.h> has ALWAYS been read
 * by the time we get here, and the typedefs below could never have been
 * anything but a redeclaration on a CRTL that declares them.
 *
 * So: the header is used unless you say otherwise, and the escape hatch is an
 * explicit VMS_NO_INTPTR_T for a CRTL old enough not to declare the pair.
 * CONFIGURE.COM still runs the probe - it compiles <inttypes.h> plus a
 * declaration that actually USES intptr_t, which a header that exists but
 * declares nothing would fail - and passes VMS_NO_INTPTR_T only when that
 * probe fails.  A negative macro is the right shape here because it makes the
 * common case need no macro at all, so a build with no CONFIGURE.COM run
 * behind it (plain "$ MMK") does the right thing rather than the rare thing.
 *
 * The fallback types are int/unsigned int, which is pointer-width in the
 * default 32-bit-pointer model documented above.  If you ever build with
 * /POINTER_SIZE=64 the fallback is wrong - but so is the rest of the engine.
 *
 * WHY THIS IS NOT SPELLED "HAVE_INTTYPES_H".  That name is already taken and it
 * answers a different question: SDL_config.h defines HAVE_INTTYPES_H
 * unconditionally, i.e. "SDL says the header exists" - not "this CRTL declares
 * intptr_t".  Reusing it would tie our answer to SDL's assumption.
 * -------------------------------------------------------------------------- */
#ifdef VMS_NO_INTPTR_T
typedef int           intptr_t;
typedef unsigned int  uintptr_t;
#else
# include <inttypes.h>
#endif

/* --------------------------------------------------------------------------
 * Fundamental types.  These are the GLib spellings, with GLib's guarantees:
 * gint16/guint16 are exactly 16 bits, gint32/guint32 exactly 32,
 * gint64/guint64 exactly 64.  On OpenVMS Alpha: short 16, int 32, long 32,
 * long long 64.
 *
 * "long long" rather than "__int64": HP C++ on OpenVMS accepts long long in
 * all dialects we build with, and CSBTypes.h already assumes a working
 * 64-bit type for i64/ui64 (the recorded-playback timestamps and the
 * INT64_FMT "%lld" format string depend on it).
 * -------------------------------------------------------------------------- */
typedef char                 gchar;
typedef unsigned char        guchar;
typedef short                gint16;
typedef unsigned short       guint16;
typedef int                  gint;
typedef unsigned int         guint;
typedef int                  gint32;
typedef unsigned int         guint32;
typedef long long            gint64;
typedef unsigned long long   guint64;
typedef int                  gboolean;
typedef void                *gpointer;
typedef const void          *gconstpointer;
typedef float                gfloat;
typedef double               gdouble;
typedef size_t               gsize;

#ifndef FALSE
# define FALSE 0
#endif
#ifndef TRUE
# define TRUE  (!FALSE)
#endif

/* --------------------------------------------------------------------------
 * Byte order.  See the ENDIANNESS note in the file header.
 * -------------------------------------------------------------------------- */
#define G_LITTLE_ENDIAN 1234
#define G_BIG_ENDIAN    4321
#define G_BYTE_ORDER    G_LITTLE_ENDIAN

/* --------------------------------------------------------------------------
 * Integer <-> pointer casts.
 *
 * CSBlinux.cpp stuffs a small event code (IDC_Timer, IDC_VIDEOEXPOSE, a
 * window width or height) into the void* user-data slots of SDL_AddTimer and
 * SDL_UserEvent, then pulls it back out.  GLib's macros go through
 * (g)size/(g)intptr precisely so that this round-trip is not a
 * pointer-truncation warning; the same reasoning applies verbatim here.
 * -------------------------------------------------------------------------- */
#define GINT_TO_POINTER(i)      ((gpointer)(intptr_t)(i))
#define GPOINTER_TO_INT(p)      ((gint)(intptr_t)(p))
#define GUINT_TO_POINTER(u)     ((gpointer)(uintptr_t)(u))
#define GPOINTER_TO_UINT(p)     ((guint)(uintptr_t)(p))

/* --------------------------------------------------------------------------
 * Logging.
 *
 * These are the four GLib entry points the compiled sources call, with GLib's
 * semantics preserved where the semantics matter:
 *
 *   g_print    -> stdout, no added newline, no prefix.  Used only in
 *                 commented-out tracing in this tree, but provided so that
 *                 re-enabling any of those lines does not break the build.
 *   g_warning  -> stderr, "** WARNING **: " prefix, newline added.
 *                 Recoverable: LinCSBUI.cpp reports a caught engine
 *                 exception with it and then returns UI_STATUS_TERMINATE.
 *   g_critical -> stderr, "** CRITICAL **: " prefix, newline added.
 *                 CSBlinux.cpp uses it when SDL_Init fails - and then keeps
 *                 going, exactly as the Linux build does, so this must NOT
 *                 abort.
 *   g_error    -> stderr, "** ERROR **: " prefix, newline, then TERMINATES.
 *                 In GLib g_error is documented as fatal and always aborts;
 *                 CSBlinux.cpp relies on that (it calls g_error when
 *                 SDL_CreateRGBSurface or the SDL timer subsystem fails and
 *                 then falls through to code that would dereference the NULL
 *                 surface).  So it exits, and it is marked noreturn-ish by
 *                 ending in exit() - CXX cannot be told noreturn portably in
 *                 C++03, which is one reason DESCRIP.MMS disables the
 *                 MISSINGRETURN warning.
 *
 * They are inline in the header rather than in a .cpp so that no extra object
 * has to be added to the 41-object build set in DESCRIP.MMS.  Each is used at
 * most a handful of times, so the code duplication is negligible.
 *
 * NOTE the deliberate asymmetry with g_log: g_log is only DECLARED here.
 * CSBlinux.cpp defines it (as a die() stub, since nothing in this tree calls
 * it), and two definitions would be a duplicate-symbol link error.
 * -------------------------------------------------------------------------- */
typedef enum
{
  G_LOG_FLAG_RECURSION = 1 << 0,
  G_LOG_FLAG_FATAL     = 1 << 1,
  G_LOG_LEVEL_ERROR    = 1 << 2,
  G_LOG_LEVEL_CRITICAL = 1 << 3,
  G_LOG_LEVEL_WARNING  = 1 << 4,
  G_LOG_LEVEL_MESSAGE  = 1 << 5,
  G_LOG_LEVEL_INFO     = 1 << 6,
  G_LOG_LEVEL_DEBUG    = 1 << 7
} GLogLevelFlags;

/* Defined by CSBlinux.cpp - see the note above. */
void g_log(const gchar *domain, GLogLevelFlags level, const gchar *format, ...);

inline void g_print(const gchar *format, ...)
{
  va_list ap;
  va_start(ap, format);
  vfprintf(stdout, format, ap);
  va_end(ap);
  fflush(stdout);
}

inline void g_warning(const gchar *format, ...)
{
  va_list ap;
  fputs("\n** WARNING **: ", stderr);
  va_start(ap, format);
  vfprintf(stderr, format, ap);
  va_end(ap);
  fputs("\n", stderr);
  fflush(stderr);
}

inline void g_critical(const gchar *format, ...)
{
  va_list ap;
  fputs("\n** CRITICAL **: ", stderr);
  va_start(ap, format);
  vfprintf(stderr, format, ap);
  va_end(ap);
  fputs("\n", stderr);
  fflush(stderr);
}

inline void g_error(const gchar *format, ...)
{
  va_list ap;
  fputs("\n** ERROR **: ", stderr);
  va_start(ap, format);
  vfprintf(stderr, format, ap);
  va_end(ap);
  fputs("\n", stderr);
  fflush(stderr);
  exit(EXIT_FAILURE);
}

/* GLib's g_assert is a hard, always-compiled assertion (unlike ASSERT in
 * CSBTypes.h, which compiles away unless _DEBUG).  No file in the OpenVMS
 * build set uses it - only LinCSBUI_orig.cpp, which is not compiled - but it
 * is a two-line macro and its absence would be a mystifying error if that
 * file were ever revived. */
#define g_assert(expr)                                                        \
  do {                                                                        \
    if (!(expr))                                                              \
    {                                                                         \
      fprintf(stderr, "\n** ERROR **: assertion failed: %s (%s:%d)\n",        \
              #expr, __FILE__, __LINE__);                                     \
      exit(EXIT_FAILURE);                                                     \
    };                                                                        \
  } while (0)

#endif /* __VMSGLIB_H__ */
