(********************************************************************)
(*                                                                  *)
(*  stritext.s7i  string text library (uses an array string)        *)
(*  Copyright (C) 2007, 2008, 2011 - 2013, 2020  Thomas Mertes      *)
(*                2021, 2023, 2024  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 "text.s7i";


(**
 *  [[text|Text]] implementation type for files stored in an [[array]] [[string]].
 *)
const type: striText is sub null_file struct
    var array string: content is 0 times "";
    var integer: line is 1;
    var integer: column is 1;
  end struct;

type_implements_interface(striText, text);


(**
 *  Open a ''striText'' with the given [[array]] [[string]] content.
 *  @return the file opened.
 *)
const func file: openStriText (in array string: content) is func
  result
    var file: newFile is STD_NULL;
  local
    var striText: new_striText is striText.value;
  begin
    new_striText.content := content;
    newFile := toInterface(new_striText);
  end func;


(**
 *  Write the [[string]] ''stri'' to ''outStriText''.
 *)
const proc: write (inout striText: outStriText, in string: stri) is func
  begin
    while outStriText.line > length(outStriText.content) do
      outStriText.content &:= "";
    end while;
    if outStriText.column > succ(length(outStriText.content[outStriText.line])) then
      outStriText.content[outStriText.line] &:=
          "" lpad (outStriText.column - succ(length(outStriText.content[outStriText.line])));
    end if;
    outStriText.content[outStriText.line] :=
        outStriText.content[outStriText.line][.. pred(outStriText.column)] &
        stri &
        outStriText.content[outStriText.line][outStriText.column ..];
    outStriText.column +:= length(stri);
  end func;


const proc: writeln (inout striText: outStriText) is func
  begin
    if outStriText.line <= length(outStriText.content) then
      outStriText.content[outStriText.line] := outStriText.content[outStriText.line][.. outStriText.column];
      outStriText.content := outStriText.content[.. outStriText.line] &
          [] (outStriText.content[outStriText.line][outStriText.column ..]) &
          outStriText.content[succ(outStriText.line) ..];
    else
      while outStriText.line > length(outStriText.content) do
        outStriText.content &:= "";
      end while;
      outStriText.content &:= "";
    end if;
    incr(outStriText.line);
    outStriText.column := 1;
  end func;


const proc: moveLeft (inout striText: outStriText, in string: stri) is func
  begin
    if outStriText.column > length(stri) then
      outStriText.column -:= length(stri);
    elsif outStriText.line > 1 then
      decr(outStriText.line);
      if outStriText.line <= length(outStriText.content) then
        outStriText.column := succ(length(outStriText.content[outStriText.line]));
      else
        outStriText.column := 1;
      end if;
    end if;
  end func;


(**
 *  Read a character from ''inStriText''.
 *  @return the character read, or [[char#EOF|EOF]] at the end of the file.
 *)
const func char: getc (inout striText: inStriText) is func
  result
    var char: charRead is ' ';
  begin
    if inStriText.line <= length(inStriText.content) then
      if inStriText.column <= length(inStriText.content[inStriText.line]) then
        charRead := inStriText.content[inStriText.line][inStriText.column];
        incr(inStriText.column);
      else
        charRead := '\n';
        incr(inStriText.line);
        inStriText.column := 1;
      end if;
    else
      charRead := EOF;
    end if;
  end func;


(**
 *  Read a [[string]] with a maximum length from ''inStriText''.
 *  @return the string read.
 *  @exception RANGE_ERROR The parameter ''maxLength'' is negative.
 *)
const func string: gets (inout striText: inStriText, in var integer: maxLength) is func
  result
    var string: striRead is "";
  begin
    if maxLength <= 0 then
      if maxLength <> 0 then
        raise RANGE_ERROR;
      end if;
    elsif inStriText.line <= length(inStriText.content) then
      if maxLength > succ(length(inStriText.content[inStriText.line]) - inStriText.column) then
        striRead := inStriText.content[inStriText.line][inStriText.column ..];
        striRead &:= "\n";
        maxLength -:= length(inStriText.content[inStriText.line]) - inStriText.column + 2;
        incr(inStriText.line);
        while inStriText.line <= length(inStriText.content) and
            maxLength > length(inStriText.content[inStriText.line]) do
          striRead &:= inStriText.content[inStriText.line];
          striRead &:= "\n";
          maxLength -:= succ(length(inStriText.content[inStriText.line]));
          incr(inStriText.line);
        end while;
        if inStriText.line <= length(inStriText.content) and maxLength > 0 then
          striRead &:= inStriText.content[inStriText.line][inStriText.column len maxLength];
          inStriText.column := succ(maxLength);
        else
          inStriText.column := 1;
        end if;
      else
        striRead := inStriText.content[inStriText.line][inStriText.column fixLen maxLength];
        inStriText.column +:= maxLength;
      end if;
    end if;
  end func;


const func string: getwd (inout striText: inStriText) is func
  result
    var string: stri is "";
  begin
    noop; # todo
  end func;


(**
 *  Read a line from ''inStriText''.
 *  A striText works as if all lines end with '\n'.
 *  The line ending character is not copied into the string. When
 *  the function is left inStriText.bufferChar contains '\n' or
 *  [[char#EOF|EOF]].
 *  @return the line read.
 *)
const func string: getln (inout striText: inStriText) is func
  result
    var string: stri is "";
  begin
    if inStriText.line <= length(inStriText.content) then
      stri := inStriText.content[inStriText.line][inStriText.column .. ];
      incr(inStriText.line);
      inStriText.column := 1;
      inStriText.bufferChar := '\n';
    else
      stri := "";
      inStriText.bufferChar := EOF;
    end if;
  end func;


(**
 *  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 striText: inStriText) is
  return inStriText.line > length(inStriText.content);


(**
 *  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 striText: inStriText) is
  return inStriText.line <= length(inStriText.content);


(**
 *  Obtain the length of a ''aStriText''.
 *  The file length is measured in characters.
 *  @return the length of a file.
 *)
const func integer: length (in striText: aStriText) is func
  result
    var integer: length is 0;
  local
    var integer: line is 0;
  begin
    for line range 1 to length(aStriText.content) do
      length +:= succ(length(aStriText.content[line]));
    end for;
  end func;


const proc: seek (in striText: aStriText, in integer: position) is func
  begin
    noop; # todo
  end func;


(**
 *  Obtain the current file position of ''aStriText''.
 *  The file position is measured in characters from the start of the file.
 *  The first character in the file has the position 1.
 *  @return the current file position.
 *)
const func integer: tell (in striText: aStriText) is func
  result
    var integer: position is 0;
  local
    var integer: line is 0;
  begin
    for line range 1 to pred(aStriText.line) do
      position +:= succ(length(aStriText.content[aStriText.line]));
    end for;
    position +:= aStriText.column;
  end func;


(**
 *  Get the height of ''aStriText''.
 *  @return the height of ''aStriText''.
 *)
const func integer: height (in striText: aStriText) is
  return length(aStriText.content);


(**
 *  Get the width of ''aStriText''.
 *  @return the width of ''aStriText''.
 *)
const func integer: width (in striText: aStriText) is func
  result
    var integer: width is 0;
  local
    var integer: line is 0;
  begin
    for line range 1 to length(aStriText.content) do
      if length(aStriText.content[line]) > width then
        width := length(aStriText.content[line]);
      end if;
    end for;
  end func;


(**
 *  Determine the current line of ''aStriText''.
 *  @return the current line of ''aStriText''.
 *)
const func integer: line (in striText: aStriText) is
  return aStriText.line;


(**
 *  Determine the current column of ''aStriText''.
 *  @return the current column of ''aStriText''.
 *)
const func integer: column (in striText: aStriText) is
  return aStriText.column;


(**
 *  Set the current position of ''aStriText'' to ''line'' and ''column''.
 *)
const proc: setPos (inout striText: aStriText, in integer: line, in integer: column) is func
  begin
    aStriText.line := line;
    aStriText.column := column;
  end func;


(**
 *  Set the ''line'' of the current position of ''aStriText''.
 *)
const proc: setLine (inout striText: aStriText, in integer: line) is func
  begin
    aStriText.line := line;
  end func;


(**
 *  Set the ''column'' of the current position of ''aStriText''.
 *)
const proc: setColumn (inout striText: aStriText, in integer: column) is func
  begin
    aStriText.column := column;
  end func;


const proc: clear (inout striText: aText) is noop;
const proc: clear (inout striText: aText, in integer: upper, in integer: left,
    in integer: lower, in integer: right) is noop;
const proc: v_scroll (inout striText: aText, in integer: count) is noop;
const proc: v_scroll (inout striText: aText, in integer: upper, in integer: left,
    in integer: lower, in integer: right, in integer: count) is noop;
const proc: h_scroll (inout striText: aText, in integer: count) is noop;
const proc: h_scroll (inout striText: aText, in integer: upper, in integer: left,
    in integer: lower, in integer: right, in integer: count) is noop;
const proc: color (inout striText: aText, in color: foreground) is noop;
const proc: color (inout striText: aText, in color: foreground, in color: background) is noop;
const proc: setPosXY (inout striText: aText, in integer: xPos, in integer: yPos) is noop;