//! @file a68g-bits.c
//! @author J. Marcel van der Veer

//! @section Copyright
//!
//! This file is part of Algol68G - an Algol 68 compiler-interpreter.
//! Copyright 2001-2026 J. Marcel van der Veer [algol68g@algol68genie.nl].

//! @section License
//!
//! 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/].

//! @section Synopsis
//!
//! Miscellaneous routines.

#include "a68g.h"
#include "a68g-prelude.h"
#include "a68g-mp.h"
#include "a68g-numbers.h"
#include "a68g-genie.h"
#include "a68g-postulates.h"
#include "a68g-parser.h"
#include "a68g-options.h"
#include "a68g-optimiser.h"
#include "a68g-listing.h"

#if defined (HAVE_R_MATHLIB)
  #include <Rmath.h>
#endif

#if defined (BUILD_WINDOWS)
  #include <windows.h>
#endif

#define WRITE_TXT(fn, txt) ASSERT (write ((fn), (txt), 1 + strlen (txt)) != -1)

#if defined (BUILD_LINUX) && defined (HAVE_EXECINFO_H)

  #include <execinfo.h>

  //! @brief Provide a rudimentary backtrace.
  
  void a68g_stack_backtrace (void)
  {
  #define DEPTH 16
    void *array[DEPTH];
    WRITE_TXT (2, "\n++++ Top of call stack:");
    size_t size = backtrace (array, DEPTH);
    if (size > 0) {
      WRITE_TXT (2, "\n");
      backtrace_symbols_fd (array, size, 2);
    }
  #undef DEPTH
  }
  
  void genie_sigsegv (NODE_T *p)
  {
    (void) p;
    raise (SIGSEGV);
  }
  
  void genie_backtrace (NODE_T *p)
  {
    (void) p;
    a68g_stack_backtrace ();
  }
#else
  void a68g_stack_backtrace (void)
  {
    WRITE_TXT (2, "\n++++ Stack backtrace is not available on this platform");
  }
  
  void genie_backtrace (NODE_T *p)
  {
    (void) p;
    a68g_stack_backtrace ();
  }

#endif

//! @brief Whether x matches c; case insensitive.

BOOL_T match_string (char *x, char *c, char stop)
{
#define DONE(z) ((z) == NULL_CHAR || (z) == stop) 
#define NOT_DONE(z) (! DONE (z))
  BOOL_T match = A68G_TRUE;
// Uppercase characters in c are mandatory.
  while ((IS_UPPER (c[0]) || IS_DIGIT (c[0]) || (NOT_DONE (x[0]) && c[0] == '-')) && match) {
    match = (BOOL_T) (match & (TO_LOWER (x[0]) == TO_LOWER ((c++)[0])));
    if (NOT_DONE (x[0])) {
      x++;
    }
  }
// Lowercase characters in c are optional.
  while (NOT_DONE (x[0]) && NOT_DONE (c[0]) && match) {
    match = (BOOL_T) (match & (TO_LOWER ((x++)[0]) == TO_LOWER ((c++)[0])));
  }
  return (BOOL_T) (match ? DONE (x[0]) : A68G_FALSE);
#undef DONE
#undef NOT_DONE
}

//! @brief Open a file in ~/.a68g, if possible.

FILE *a68g_fopen (char *fn, char *mode, char *new_fn)
{
  #if defined (BUILD_WINDOWS) || !defined (HAVE_DIRENT_H)
    ASSERT (a68g_bufprt (new_fn, SNPRINTF_SIZE, "%s", fn) >= 0);
    return fopen (new_fn, mode);
  #else
    errno = 0;
    BUFFER dn;
    ASSERT (a68g_bufprt (dn, SNPRINTF_SIZE, "%s/%s", getenv ("HOME"), A68G_DIR) >= 0);
    int ret = mkdir (dn, (mode_t) (S_IRUSR | S_IWUSR | S_IXUSR));
    if (ret == 0 || (ret == -1 && errno == EEXIST)) {
      struct stat status;
      if (stat (dn, &status) == 0 && S_ISDIR (ST_MODE (&status)) != 0) {
        FILE *f;
        ASSERT (a68g_bufprt (new_fn, SNPRINTF_SIZE, "%s/%s", dn, fn) >= 0);
        f = fopen (new_fn, mode);
        if (f != NO_FILE) {
          return f;
        }
      }
    }
    ASSERT (a68g_bufprt (new_fn, SNPRINTF_SIZE, "%s", fn) >= 0);
    return fopen (new_fn, mode);
  #endif
}

//! @brief Get terminal size.

void a68g_getty (int *h, int *c)
{
// Default action first.
  (*h) = MAX_TERM_HEIGTH;
  (*c) = MAX_TERM_WIDTH;
  #if (defined (HAVE_SYS_IOCTL_H) && defined (TIOCGWINSZ))
    {
      struct winsize w;
      if (ioctl (0, TIOCGWINSZ, &w) == 0) {
        (*h) = w.ws_row;
        (*c) = w.ws_col;
      }
    }
  #elif (defined (HAVE_SYS_IOCTL_H) && defined (TIOCGSIZE))
    {
      struct ttysize w;
      (void) ioctl (0, TIOCGSIZE, &w);
      if (w.ts_lines > 0) {
        (*h) = w.ts_lines;
      }
      if (w.ts_cols > 0) {
        (*c) = w.ts_cols;
      }
    }
  #elif (defined (HAVE_SYS_IOCTL_H) && defined (WIOCGETD))
    {
      struct uwdata w;
      (void) ioctl (0, WIOCGETD, &w);
      if (w.uw_heigth > 0 && w.uw_vs != 0) {
        (*h) = w.uw_heigth / w.uw_vs;
      }
      if (w.uw_width > 0 && w.uw_hs != 0) {
        (*c) = w.uw_width / w.uw_hs;
      }
    }
  #endif
}

// Signal handlers.

#if defined (SIGWINCH)

//! @brief Signal for window resize.

void sigwinch_handler (int i)
{
  (void) i;
  #if !defined (HAVE_SIGACTION)
    ABEND (signal (SIGWINCH, sigwinch_handler) == SIG_ERR, ERROR_ACTION, NO_TEXT);
  #endif
  a68g_getty (&A68G (term_heigth), &A68G (term_width));
}

//! @brief Install_signal_handlers.

void install_sigwinch (void)
{
  #if defined (HAVE_SIGACTION)
    struct sigaction sa;
    a68g_bufset (&sa, 0, sizeof (sa));
    sa.sa_handler = sigwinch_handler;
    ABEND (sigaction (SIGWINCH, &sa, NULL) != 0, ERROR_ACTION, NO_TEXT);
  #else
    ABEND (signal (SIGWINCH, sigwinch_handler) == SIG_ERR, ERROR_ACTION, NO_TEXT);
  #endif
}

#endif

//! @brief Signal handler for segment violation.

void sigsegv_handler (int i)
{
  (void) i;
// write () is asynchronous-safe and may be called here.
  WRITE_TXT (2, "\nFatal");
  if (FILE_INITIAL_NAME (&A68G_JOB) != NO_TEXT) {
    WRITE_TXT (2, ": ");
    WRITE_TXT (2, FILE_INITIAL_NAME (&A68G_JOB));
  }
  WRITE_TXT (2, ": memory access violation\n");
  #if defined (BUILD_LINUX) && defined (HAVE_EXECINFO_H)
    a68g_stack_backtrace ();
  #endif
  exit (EXIT_FAILURE);
}

//! @brief Install_signal_handlers.

void install_sigsegv (void)
{
  #if defined (HAVE_SIGACTION)
    struct sigaction sa;
    a68g_bufset (&sa, 0, sizeof (sa));
    sa.sa_handler = sigsegv_handler;
    ABEND (sigaction (SIGSEGV, &sa, NULL) != 0, ERROR_ACTION, NO_TEXT);
  #else
    ABEND (signal (SIGSEGV, sigsegv_handler) == SIG_ERR, ERROR_ACTION, NO_TEXT);
  #endif
}

//! @brief Raise SYSREQUEST so you get to a monitor.

void sigint_handler (int i)
{
  (void) i;
  #if !defined (HAVE_SIGACTION)
    ABEND (signal (SIGINT, sigint_handler) == SIG_ERR, ERROR_ACTION, NO_TEXT);
  #endif
  if (!(STATUS_TEST (TOP_NODE (&A68G_JOB), BREAKPOINT_INTERRUPT_MASK) || A68G (in_monitor))) {
    STATUS_SET (TOP_NODE (&A68G_JOB), BREAKPOINT_INTERRUPT_MASK);
    genie_break (TOP_NODE (&A68G_JOB));
  }
}

//! @brief Install_signal_handlers.

void install_sigint (void)
{
  #if defined (HAVE_SIGACTION)
    struct sigaction sa;
    a68g_bufset (&sa, 0, sizeof (sa));
    sa.sa_handler = sigint_handler;
    ABEND (sigaction (SIGINT, &sa, NULL) != 0, ERROR_ACTION, NO_TEXT);
  #else
    ABEND (signal (SIGINT, sigint_handler) == SIG_ERR, ERROR_ACTION, NO_TEXT);
  #endif
}

#if defined (BUILD_UNIX)

//! @brief Signal handler from disconnected terminal.

void sigttin_handler (int i)
{
  (void) i;
  ABEND (A68G_TRUE, ERROR_ACTION, NO_TEXT);
}

//! @brief Install_signal_handlers.

void install_sigttin (void)
{
  #if defined (HAVE_SIGACTION)
    struct sigaction sa;
    a68g_bufset (&sa, 0, sizeof (sa));
    sa.sa_handler = sigttin_handler;
    ABEND (sigaction (SIGTTIN, &sa, NULL) != 0, ERROR_ACTION, NO_TEXT);
  #endif
}

//! @brief Signal broken pipe.

void sigpipe_handler (int i)
{
  (void) i;
  ABEND (A68G_TRUE, ERROR_ACTION, NO_TEXT);
}

//! @brief Install_signal_handlers.

void install_sigpipe (void)
{
  #if defined (HAVE_SIGACTION)
    struct sigaction sa;
    a68g_bufset (&sa, 0, sizeof (sa));
    sa.sa_handler = sigpipe_handler;
    ABEND (sigaction (SIGPIPE, &sa, NULL) != 0, ERROR_ACTION, NO_TEXT);
  #endif
}

//! @brief Signal alarm - time limit check.

unsigned a68g_alarm (unsigned seconds)
{
  struct itimerval old, new;
  new.it_interval.tv_usec = 0;
  new.it_interval.tv_sec = 0;
  new.it_value.tv_usec = 0;
  new.it_value.tv_sec = (long int) seconds;
  if (setitimer (ITIMER_REAL, &new, &old) < 0) {
    return 0;
  } else {
    return old.it_value.tv_sec;
  }
}

//! @brief SIGALRM handler.

void sigalrm_handler (int i)
{
  (void) i;
  if (A68G (in_execution) && !A68G (in_monitor)) {
    REAL_T _m_t = (REAL_T) OPTION_TIME_LIMIT (&A68G_JOB);
    if (_m_t > 0 && (seconds () - A68G (cputime_0)) > _m_t) {
      diagnostic (A68G_RUNTIME_ERROR, (NODE_T *) A68G (f_entry), ERROR_TIME_LIMIT_EXCEEDED);
      exit_genie ((NODE_T *) A68G (f_entry), A68G_RUNTIME_ERROR);
    }
  }
  (void) a68g_alarm (A68G_SIGALRM_INTERVAL);
}

//! @brief Install_signal_handlers.

void install_sigalrm (void)
{
  #if defined (HAVE_SIGACTION)
    struct sigaction sa;
    a68g_bufset (&sa, 0, sizeof (sa));
    sa.sa_handler = sigalrm_handler;
    if (OPTION_RESTART (&A68G_JOB)) { // Default
      sa.sa_flags = SA_RESTART;
    }
    ABEND (sigaction (SIGALRM, &sa, NULL) != 0, ERROR_ACTION, NO_TEXT);
  #endif
}

#endif

//! @brief Install_signal_handlers.

void install_signal_handlers (void)
{
  install_sigint ();
  install_sigsegv ();
  #if defined (SIGWINCH)
    install_sigwinch ();
  #endif
  #if defined (BUILD_UNIX)
    install_sigalrm ();
    install_sigpipe ();
    install_sigttin ();
  #endif
}

//! @brief Wall clock versus arbitrary origin.

REAL_T wall_seconds (void)
{
  #if defined (HAVE_RTLIB)
    struct timespec ts;
    clock_gettime (CLOCK_MONOTONIC, &ts);
    return ts.tv_sec + ts.tv_nsec * 1e-9; 
  #else
    return seconds();
  #endif
} 

//! @brief Process time versus arbitrary origin.

REAL_T seconds (void)
{
  return (REAL_T) clock () / (REAL_T) CLOCKS_PER_SEC;
}

//! @brief Delay for specified number of microseconds.

int a68g_usleep (unsigned delay)
{
  #if defined (BUILD_WINDOWS)
    errno = ENOSYS; // Function not implemented.
    return -1; 
  #else
// usleep is not compatible with _XOPEN_SOURCE 700.
    struct timespec reqtime;
    reqtime.tv_sec = delay / 1000000;
    reqtime.tv_nsec = 1000 * (delay % 1000000);
    return nanosleep (&reqtime, NULL);
  #endif
}

//! @brief Safely set buffer.

void *a68g_bufset (void *dst, int val, size_t len)
{
// Function memset can be optimized away by a compiler.
// Therefore we have this alternative.
// This works because sizeof (char) == 1 is true by convention.
  ASSERT (dst != NO_TEXT);
  char *p = (char *) dst;
  while (len-- > 0) {
    *(p++) = val;
  }
  return dst;
}

//! @brief Safely append to buffer.

void a68g_bufcat (char *dst, char *src, size_t len)
{
  ASSERT (dst != NO_TEXT);
  ASSERT (src != NO_TEXT);
  char *d = dst, *s = src;
  int n = len;
// Find end of dst and left-adjust; do not go past end.
  for (; n-- != 0 && d[0] != NULL_CHAR; d++) {
    ;
  }
  int dlen = (int) (d - dst);
  n = len - dlen;
  if (n > 0) {
    while (s[0] != NULL_CHAR) {
      if (n != 1) {
        (d++)[0] = s[0];
        n--;
      }
      s++;
    }
    d[0] = NULL_CHAR;
  }
// Better sure than sorry.
  dst[len - 1] = NULL_CHAR;
}

//! @brief Safely copy to buffer.

void a68g_bufcpy (char *dst, char *src, size_t len)
{
  ASSERT (dst != NO_TEXT);
  ASSERT (src != NO_TEXT);
  char *d = dst, *s = src;
  int n = len;
// Copy as many as fit. 
  if (n > 0 && --n > 0) {
    do {
      if (((d++)[0] = (s++)[0]) == NULL_CHAR) {
        break;
      }
    } while (--n > 0);
  }
  if (n == 0 && len > 0) {
// Not enough room in dst, so terminate. 
    d[0] = NULL_CHAR;
  }
// Better sure than sorry. 
  dst[len - 1] = NULL_CHAR;
}

//! @brief Safely print to buffer.

int a68g_bufprt (char *dst, size_t len, const char *format, ...)
{
  ASSERT (dst != NO_TEXT);
  ASSERT (len > 1);
  ASSERT (format != NO_TEXT);
  va_list ap;
  va_start (ap, format);
  int rc = vsnprintf (dst, len, format, ap);
  va_end (ap);
// Better sure than sorry.
  dst[len - 1] = NULL_CHAR;
  if (rc >= 0 && len <= (size_t) rc) {
    return -1;
  } else {
    return rc;
  }
}

//! @brief Take a peek at stdin.

int peek_char (int mode)
{
#if defined (BUILD_WINDOWS)
  if (mode & A68G_PEEK_RESET) {
    A68G (stdin_is_raw) = A68G_FALSE;
    return NULL_CHAR;
  }
  if (mode & A68G_PEEK_INIT) {
    A68G (stdin_is_raw) = A68G_TRUE;
  } 
  if (mode & A68G_PEEK_READ) {
    if (kbhit ()) {
      return getch ();
    }
  }
  return NULL_CHAR;
#elif defined (BUILD_UNIX)
  const tcflag_t mask = ICANON | ECHO;
  static struct termios cpy;
  if (mode & A68G_PEEK_RESET) {
    if (A68G (stdin_is_raw) == A68G_TRUE) {
      ASSERT (tcsetattr (A68G_STDIN, TCSADRAIN, &cpy) >= 0);
      A68G (stdin_is_raw) = A68G_FALSE;
    }
    return NULL_CHAR;
  }
  if (mode & A68G_PEEK_INIT) {
    if (A68G (stdin_is_raw) == A68G_FALSE) {
      struct termios tty;
      ASSERT (tcgetattr (A68G_STDIN, &tty) >= 0);
      cpy = tty;
      tty.c_lflag &= ~mask;
      tty.c_iflag |= BRKINT;
      tty.c_cc[VMIN] = 0; // Set to 1 for blocking operation.
      tty.c_cc[VTIME] = 0;
      ASSERT (tcsetattr (A68G_STDIN, TCSANOW, &tty) >= 0);
      A68G (stdin_is_raw) = A68G_TRUE;
    }
  }
  if (mode & A68G_PEEK_READ) {
    char buf = NULL_CHAR;
    ASSERT (read (A68G_STDIN, &buf, 1) >= 0);
    return (buf);
  }
  return NULL_CHAR;
#else
  (void) mode;
  return NULL_CHAR;
#endif
}

