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

//! @section Copyright
//!
//! This file is part of Algol68G - an Algol 68 compiler-interpreter.
//! Copyright 2001-2025 J. Marcel van der Veer [algol68g@xs4all.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
//!
//! Low-level input-output routines.

// These routines fail in principle when 'read' or 'write' are interrupted 
// MAX_RESTART_AT_INTERRUPT times, for instance by SIGALRM.
//
// On platforms that support it, SIGALRM is used to implement a CPU time out 
// on a running Algol 68 program. 
//
// The SIGALRM handler however implements BSD type restarting of primitive
// system call interupted by it, which includes 'read' and 'write'. 
// Consider however that non-responsive devices could make a68g 'hang'. 
// In that case, specify option NORESTART, and SIGALRM will interrupt IO.
// Current settings make these routines fail after 10 minutes of trying.

#include "a68g.h"
#include "a68g-prelude.h"

//! @brief Initialise output to STDOUT.

void init_tty (void)
{
  A68G (chars_in_stderr) = 0;
  A68G (chars_in_stdout) = 0;
  A68G (halt_typing) = A68G_FALSE;
  change_masks (TOP_NODE (&A68G_JOB), BREAKPOINT_INTERRUPT_MASK, A68G_FALSE);
}

//! @brief Terminate current line on STDOUT.

void io_close_tty_line (void)
{
  if (A68G (chars_in_stderr) > 0) {
    A68G (chars_in_stderr) = 0;
    io_write_string (A68G_STDERR, NEWLINE_STRING);
  }
  if (A68G (chars_in_stdout) > 0) {
    A68G (chars_in_stdout) = 0;
    io_write_string (A68G_STDOUT, NEWLINE_STRING);
  }
}

//! @brief Get a char from STDIN.

char get_stdin_char (void)
{
  char ch[4];
  errno = 0;
  ssize_t j = io_read_conv (A68G_STDIN, &(ch[0]), 1);
  ABEND (j < 0, ERROR_ACTION, NO_TEXT);
  return (char) (j == 1 ? ch[0] : EOF_CHAR);
}

//! @brief Read string from STDIN, until NEWLINE_STRING.

char *read_string_from_tty (char *prompt)
{
  #if defined (HAVE_READLINE)
    char *line = readline (prompt);
    if (line != NO_TEXT && strlen (line) > 0) {
      add_history (line);
    }
    a68g_bufcpy (A68G (input_line), line, BUFFER_SIZE);
    A68G (chars_in_stdout) = strlen (A68G (input_line));
    a68g_free (line);
    return A68G (input_line);
  #else
    if (prompt != NO_TEXT) {
      io_close_tty_line ();
      io_write_string (A68G_STDOUT, prompt);
    }
    int ch = get_stdin_char (), k= 0;
    while (ch != NEWLINE_CHAR && k < BUFFER_SIZE - 1) {
      if (ch == EOF_CHAR) {
        A68G (input_line)[0] = EOF_CHAR;
        A68G (input_line)[1] = NULL_CHAR;
        A68G (chars_in_stdout) = 1;
        return A68G (input_line);
      } else {
        A68G (input_line)[k++] = (char) ch;
        ch = get_stdin_char ();
      }
    }
    A68G (input_line)[k] = NULL_CHAR;
    size_t n = strlen (A68G (input_line));
    A68G (chars_in_stdout) = (ch == NEWLINE_CHAR ? 0 : (n > 0 ? n : 1));
    return A68G (input_line);
  #endif
}

//! @brief Write string to file.

void io_write_string (FILE_T f, const char *z)
{
  errno = 0;
  if (f != A68G_STDOUT && f != A68G_STDERR) {
// Writing to file.
    ssize_t j = io_write_conv (f, z, strlen (z));
    ABEND (j < 0, ERROR_ACTION, NO_TEXT);
  } else {
// Writing to TTY parts until end-of-string.
    int first = 0, k;
    do {
      k = first;
// How far can we get?.
      while (z[k] != NULL_CHAR && z[k] != NEWLINE_CHAR) {
        k++;
      }
      if (k > first) {
// Write these characters.
        int n = k - first;
        ssize_t j = io_write_conv (f, &(z[first]), (size_t) n);
        ABEND (j < 0, ERROR_ACTION, NO_TEXT);
        if (f == A68G_STDERR) {
          A68G (chars_in_stderr) += n;
        } else {
          A68G (chars_in_stdout) += n;
        }
      }
      if (z[k] == NEWLINE_CHAR) {
// Pretty-print newline.
        k++;
        first = k;
        ssize_t j = io_write_conv (f, NEWLINE_STRING, 1);
        ABEND (j < 0, ERROR_ACTION, NO_TEXT);
        if (f == A68G_STDERR) {
          A68G (chars_in_stderr) = 0;
        } else {
          A68G (chars_in_stdout) = 0;
        }
      }
    } while (z[k] != NULL_CHAR);
  }
}

//! @brief Read bytes from file into buffer.

ssize_t io_read (FILE_T fd, void *buf, size_t n)
{
  size_t to_do = n;
  int retry = 0;
  char *z = (char *) buf;
  while (to_do > 0) {
    #if defined (BUILD_WIN32)
      int bytes_read;
    #elif defined (BUILD_WIN64)
      ssize_t bytes_read;
    #else
      ssize_t bytes_read;
    #endif
    errno = 0;
    bytes_read = read (fd, z, to_do);
    if (bytes_read < 0) {
      if (errno == EINTR) {
// interrupt, retry.
        bytes_read = 0;
        if (++retry > MAX_INTERRUPT_RESTART) {
          return -1;
        }
      } else {
// read error.
        return -1;
      }
    } else if (bytes_read == 0) {
      break;                    // EOF_CHAR
    }
    to_do -= (size_t) bytes_read;
    z += bytes_read;
  }
  return (ssize_t) n - (ssize_t) to_do; // return >= 0
}

//! @brief Writes n bytes from buffer to file.

ssize_t io_write (FILE_T fd, const void *buf, size_t n)
{
  size_t to_do = n;
  int retry = 0;
  char *z = (char *) buf;
  while (to_do > 0) {
    ssize_t bytes_written;
    errno = 0;
    bytes_written = write (fd, z, to_do);
    if (bytes_written <= 0) {
      if (errno == EINTR) {
// interrupt, retry.
        bytes_written = 0;
        if (++retry > MAX_INTERRUPT_RESTART) {
          return -1;
        }
      } else {
// write error.
        return -1;
      }
    }
    to_do -= (size_t) bytes_written;
    z += bytes_written;
  }
  return (ssize_t) n;
}

//! @brief Read bytes from file into buffer.

ssize_t io_read_conv (FILE_T fd, void *buf, size_t n)
{
  size_t to_do = n;
  int retry = 0;
  char *z = (char *) buf;
  while (to_do > 0) {
    #if defined (BUILD_WIN32)
      int bytes_read;
    #elif defined (BUILD_WIN64)
      ssize_t bytes_read;
    #else
      ssize_t bytes_read;
    #endif
    errno = 0;
    bytes_read = read (fd, z, to_do);
    if (bytes_read < 0) {
      if (errno == EINTR) {
// interrupt, retry.
        bytes_read = 0;
        if (++retry > MAX_INTERRUPT_RESTART) {
          return -1;
        }
      } else {
// read error.
        return -1;
      }
    } else if (bytes_read == 0) {
      break;                    // EOF_CHAR
    }
    to_do -= (size_t) bytes_read;
    z += bytes_read;
  }
  return (ssize_t) n - (ssize_t) to_do;
}

//! @brief Writes n bytes from buffer to file.

ssize_t io_write_conv (FILE_T fd, const void *buf, size_t n)
{
  size_t to_do = n;
  int retry = 0;
  char *z = (char *) buf;
  while (to_do > 0) {
    ssize_t bytes_written;
    errno = 0;
    bytes_written = write (fd, z, to_do);
    if (bytes_written <= 0) {
      if (errno == EINTR) {
// interrupt, retry.
        bytes_written = 0;
        if (++retry > MAX_INTERRUPT_RESTART) {
          return -1;
        }
      } else {
// write error.
        return -1;
      }
    }
    to_do -= (size_t) bytes_written;
    z += bytes_written;
  }
  return (ssize_t) n;
}
