(********************************************************************)
(*                                                                  *)
(*  external_file.s7i  File implementation type describing OS files *)
(*  Copyright (C) 1989 - 2014, 2019 - 2021  Thomas Mertes           *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime 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.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime 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 Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "clib_file.s7i";
include "null_file.s7i";


(**
 *  [[file|File]] implementation type describing OS files.
 *  This type provides access to sequential operating system files.
 *  External files are seekable, therefore they support the
 *  functions [[#length(in_external_file)|length]],
 *  [[#seek(in_external_file,in_integer)|seek]] and
 *  [[#tell(in_external_file)|tell]].
 *)
const type: external_file is sub null_file struct
    var clib_file: ext_file is CLIB_NULL_FILE;
    var string: name is "";
  end struct;


type_implements_interface(external_file, file);


(**
 *  Opens a file with the specified ''path'' and ''mode''.
 *  There are text modes and binary modes:
 *  *Binary modes:
 *  ** "r"   Open file for reading.
 *  ** "w"   Truncate to zero length or create file for writing.
 *  ** "a"   Append; open or create file for writing at end-of-file.
 *  ** "r+"  Open file for update (reading and writing).
 *  ** "w+"  Truncate to zero length or create file for update.
 *  ** "a+"  Append; open or create file for update, writing at end-of-file.
 *  *Text modes:
 *  ** "rt"  Open file for reading.
 *  ** "wt"  Truncate to zero length or create file for writing.
 *  ** "at"  Append; open or create file for writing at end-of-file.
 *  ** "rt+" Open file for update (reading and writing).
 *  ** "wt+" Truncate to zero length or create file for update.
 *  ** "at+" Append; open or create file for update, writing at end-of-file.
 *  Note that this modes differ from the ones used by the C function
 *  fopen().
 *  @param path Path of the file to be opened. The path must
 *         use the standard path representation.
 *  @param mode Mode of the file to be opened.
 *  @return the file opened, or [[null_file#STD_NULL|STD_NULL]]
 *          if it could not be opened or if ''path'' refers to
 *          a directory.
 *  @exception MEMORY_ERROR Not enough memory to convert the path
 *             to the system path type.
 *  @exception RANGE_ERROR The ''mode'' is not one of the allowed
 *             values or ''path'' does not use the standard path
 *             representation or ''path'' cannot be converted
 *             to the system path type.
 *)
const func file: open (in string: path, in string: mode) is func
  result
    var file: newFile is STD_NULL;
  local
    var clib_file: open_file is CLIB_NULL_FILE;
    var external_file: new_file is external_file.value;
  begin
    open_file := openClibFile(path, mode);
    if open_file <> CLIB_NULL_FILE then
      new_file.ext_file := open_file;
      new_file.name := path;
      newFile := toInterface(new_file);
    end if;
  end func;


const func file: openNullDevice is func
  result
    var file: nullDeviceFile is STD_NULL;
  local
    var clib_file: open_file is CLIB_NULL_FILE;
    var external_file: new_file is external_file.value;
  begin
    open_file := openNullDeviceClibFile;
    if open_file <> CLIB_NULL_FILE then
      new_file.ext_file := open_file;
      nullDeviceFile := toInterface(new_file);
    end if;
  end func;


(**
 *  Close an external_file.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: close (inout external_file: aFile) is func
  begin
    close(aFile.ext_file);
    aFile.ext_file := CLIB_NULL_FILE;
  end func;


(**
 *  Forces that all buffered data of ''outFile'' is sent to its destination.
 *  This causes data to be sent to the file system of the OS.
 *)
const proc: flush (in external_file: outFile) is func
  begin
    flush(outFile.ext_file);
  end func;


(**
 *  Write a [[string]] to an external_file.
 *  @exception FILE_ERROR A system function returns an error.
 *  @exception RANGE_ERROR The string contains a character that does
 *             not fit into a byte ('\0;' to '\255;' fit into a byte).
 *)
const proc: write (in external_file: outFile, in string: stri) is func
  begin
    write(outFile.ext_file, stri);
  end func;


const proc: moveLeft (in external_file: outFile, in string: stri) is func
  begin
    write(outFile.ext_file, "\b" mult length(stri));
  end func;


const proc: erase (in external_file: outFile, in string: stri) is func
  begin
    write(outFile.ext_file, " " mult length(stri));
  end func;


(**
 *  Read a character from an external_file.
 *  @return the character read, or [[char#EOF|EOF]] at the end of the file.
 *)
const func char: getc (in external_file: inFile) is
  return getc(inFile.ext_file);


(**
 *  Read a [[string]] with a maximum length from an external_file.
 *  @return the string read.
 *  @exception RANGE_ERROR The parameter ''maxLength'' is negative.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const func string: gets (in external_file: inFile, in integer: maxLength) is
  return gets(inFile.ext_file, maxLength);


(**
 *  Read a [[string]] from ''inFile'' until the ''terminator'' character is found.
 *  If a ''terminator'' is found the string before the ''terminator'' is
 *  returned and the ''terminator'' character is assigned to inFile.bufferChar.
 *  The file position is advanced after the ''terminator'' character.
 *  If no ''terminator'' is found the rest of ''inFile'' is returned and
 *  [[char#EOF|EOF]] is assigned to the inFile.bufferChar. When the function
 *  is left inFile.bufferChar contains either ''terminator'' or [[char#EOF|EOF]].
 *  @param inFile File from which the string is read.
 *  @param terminator Character which terminates the string.
 *  @return the string read without the ''terminator'' or the rest of the
 *          file if no ''terminator'' is found.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const func string: getTerminatedString (inout external_file: inFile,
                                        in char: terminator) is
  return terminated_read(inFile.ext_file, terminator, inFile.bufferChar);


(**
 *  Read a word from an external_file.
 *  Before reading the word it skips spaces and tabs. The function
 *  accepts words ending with ' ', '\t', '\n', "\r\n" or [[char#EOF|EOF]].
 *  The word ending characters are not copied into the string.
 *  That means that the '\r' of a "\r\n" sequence is silently removed.
 *  When the function is left inFile.bufferChar contains ' ',
 *  '\t', '\n' or [[char#EOF|EOF]].
 *  @return the word read.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const func string: getwd (inout external_file: inFile) is
  return word_read(inFile.ext_file, inFile.bufferChar);


(**
 *  Read a line from an external_file.
 *  The function accepts lines ending with '\n', "\r\n" or [[char#EOF|EOF]].
 *  The line ending characters are not copied into the string.
 *  That means that the '\r' of a "\r\n" sequence is silently removed.
 *  When the function is left inFile.bufferChar contains '\n' or
 *  [[char#EOF|EOF]].
 *  @return the line read.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const func string: getln (inout external_file: inFile) is
  return line_read(inFile.ext_file, inFile.bufferChar);


(**
 *  Determine the end-of-file indicator.
 *  The end-of-file indicator is set if at least one request to read
 *  from the file failed.
 *  @return TRUE if the end-of-file indicator is set, FALSE otherwise.
 *)
const func boolean: eof (in external_file: inFile) is
  return eof(inFile.ext_file);


(**
 *  Determine if at least one character can be read successfully.
 *  This function allows a file to be handled like an iterator.
 *  @return FALSE if ''getc'' would return [[char#EOF|EOF]], TRUE otherwise.
 *)
const func boolean: hasNext (in external_file: inFile) is
  return hasNext(inFile.ext_file);


(**
 *  Determine if at least one character can be read without blocking.
 *  Blocking means that ''getc'' would wait until a character is
 *  received. Blocking can last for a period of unspecified length.
 *  Regular files do not block.
 *  @return TRUE if ''getc'' would not block, FALSE otherwise.
 *)
const func boolean: inputReady (in external_file: inFile) is
  return inputReady(inFile.ext_file);


(**
 *  Obtain the length of an external_file.
 *  The file length is measured in bytes.
 *  @return the size of the given file.
 *  @exception RANGE_ERROR The file length does not fit into
 *             an integer value.
 *  @exception FILE_ERROR A system function returns an error or the
 *             file length reported by the system is negative.
 *)
const func integer: length (in external_file: aFile) is
  return length(aFile.ext_file);


(**
 *  Truncate ''aFile'' to the given ''length''.
 *  If the file previously was larger than ''length'', the extra data is lost.
 *  If the file previously was shorter, it is extended, and the extended
 *  part is filled with null bytes ('\0;').
 *  @param aFile File to be truncated.
 *  @param length Requested length of ''aFile'' in bytes.
 *  @exception RANGE_ERROR The requested length is negative or
 *             the length is not representable in the type
 *             used by the system function.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: truncate (in external_file: aFile, in integer: length) is func
  begin
    truncate(aFile.ext_file, length);
  end func;


(**
 *  Determine if the file ''aFile'' is seekable.
 *  If a file is seekable the functions ''seek'' and ''tell''
 *  can be used to set and and obtain the current file position.
 *  @return TRUE, if ''aFile'' is seekable, FALSE otherwise.
 *)
const func boolean: seekable (in external_file: aFile) is
  return seekable(aFile.ext_file);


(**
 *  Set the current file position.
 *  The file position is measured in bytes from the start of the file.
 *  The first byte in the file has the position 1.
 *  @exception RANGE_ERROR The file position is negative or zero or
 *             the file position is not representable in the system
 *             file position type.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: seek (in external_file: aFile, in integer: position) is func
  begin
    seek(aFile.ext_file, position);
  end func;


(**
 *  Obtain the current file position.
 *  The file position is measured in bytes from the start of the file.
 *  The first byte in the file has the position 1.
 *  @return the current file position.
 *  @exception RANGE_ERROR The file position does not fit into
 *             an integer value.
 *  @exception FILE_ERROR A system function returns an error or the
 *             file position reported by the system is negative.
 *)
const func integer: tell (in external_file: aFile) is
  return tell(aFile.ext_file);


const func boolean: fileOpenSucceeds (in string: path) is func
  result
    var boolean: fileOpenSucceeds is FALSE;
  local
    var file: a_file is STD_NULL;
  begin
    a_file := open(path, "r");
    if a_file <> STD_NULL then
      close(a_file);
      fileOpenSucceeds := TRUE;
    end if;
  end func;