(********************************************************************)
(*                                                                  *)
(*  window.s7i    Filter file for text windows with random access   *)
(*  Copyright (C) 1992, 1993, 1994, 2005  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 "null_file.s7i";
include "text.s7i";


(**
 *  [[text|Text]] implementation type providing a rectangular area of text.
 *  A ''window_file'' is based on an underlying text file. It provides
 *  access to an rectangular area of the underlying text file.
 *)
const type: window_file is sub null_file struct
    var text: out_file is STD_NULL;
    var integer: upper_border is 0;
    var integer: left_border is 0;
    var integer: height is 0;
    var integer: width is 0;
    var integer: curr_line is 0;
    var integer: curr_column is 0;
  end struct;


type_implements_interface(window_file, text);


(**
 *  Creates a ''window_file'' at (left, upper) in ''outText''.
 *  The ''window_file'' has the given ''height'' and ''width''.
 *  @return the file opened.
 *)
const func file: openWindow (ref text: outText,
    in integer: upper, in integer: left, in integer: height, in integer: width) is func
  result
    var file: newFile is STD_NULL;
  local
    var window_file: window_fil is window_file.value;
  begin
    window_fil.out_file := outText;
    window_fil.upper_border := upper;
    window_fil.left_border := left;
    if height(outText) = 0 or upper + height - 1 <= height(outText) then
      window_fil.height := height;
    else
      window_fil.height := height(outText) - upper + 1;
    end if;
    if width(outText) = 0 or left + width - 1 <= width(outText) then
      window_fil.width := width;
    else
      window_fil.width := width(outText) - left + 1;
    end if;
    window_fil.curr_line := 1;
    window_fil.curr_column := 1;
    newFile := toInterface(window_fil);
  end func;


(**
 *  Get the height of 'outText'.
 *  @return the height of 'outText'.
 *)
const func integer: height (in window_file: outText) is
  return outText.height;


(**
 *  Get the width of 'outText'.
 *  @return the width of 'outText'.
 *)
const func integer: width (in window_file: outText) is
  return outText.width;


(**
 *  Get the current line of ''outText''.
 *  @return the current line of ''outText''.
 *)
const func integer: line (in window_file: outText) is
  return outText.curr_line;


(**
 *  Get the current column of ''outText''.
 *  @return the current column of ''outText''.
 *)
const func integer: column (in window_file: outText) is
  return outText.curr_column;


(**
 *  Forces that all buffered data of ''aFile'' is sent to its destination.
 *  This causes data to be sent to the underlying ''text'' file.
 *)
const proc: flush (inout window_file: window_fil) is func
  begin
    if window_fil.curr_line >= 1 and
        window_fil.curr_line <= window_fil.height and
        window_fil.curr_column >= 1 and
        window_fil.curr_column <= window_fil.width then
      setPos(window_fil.out_file,
          pred(window_fil.upper_border + window_fil.curr_line),
          pred(window_fil.left_border + window_fil.curr_column));
    end if;
    flush(window_fil.out_file);
  end func;


const proc: box (ref window_file: window_fil) is func
  local
    var text: outf is STD_NULL;
    var integer: line is 1;
    var integer: start_line is 1;
    var integer: start_col is 1;
    var string: stri is "";
  begin
    outf := window_fil.out_file;
    start_col := window_fil.left_border;
    start_line := window_fil.upper_border;
    if window_fil.left_border > 1 then
      decr(start_col);
      for line range start_line to
          start_line + window_fil.height - 1 do
        setPos(outf, line, start_col);
        write(outf, "|");
      end for;
    end if;
    if window_fil.left_border + window_fil.width - 1 < width(window_fil.out_file) then
      for line range start_line to
          start_line + window_fil.height - 1 do
        setPos(outf,
            line, window_fil.left_border + window_fil.width);
        write(outf, "|");
      end for;
    end if;
    stri := "+" & "-" mult window_fil.width & "+";
    if start_line > 1 then
      setPos(outf, start_line - 1, start_col);
      write(outf, stri);
    end if;
    if start_line + window_fil.height - 1 < height(window_fil.out_file) then
      setPos(outf, start_line + window_fil.height, start_col);
      write(outf, stri);
    end if;
  end func;


const proc: clear_box (ref window_file: window_fil) is func
  local
    var text: outf is STD_NULL;
    var integer: line is 1;
    var integer: start_line is 1;
    var integer: start_col is 1;
    var string: stri is "";
  begin
    outf := window_fil.out_file;
    start_col := window_fil.left_border;
    start_line := window_fil.upper_border;
    stri := "";
    if window_fil.left_border > 1 then
      decr(start_col);
      stri := " ";
      for line range start_line to
          start_line + window_fil.height - 1 do
        setPos(outf, line, start_col);
        write(outf, " ");
      end for;
    end if;
    stri &:= " " mult window_fil.width;
    if window_fil.left_border + window_fil.width - 1 < width(window_fil.out_file) then
      stri &:= " ";
      for line range start_line to
          start_line + window_fil.height - 1 do
        setPos(outf,
            line, window_fil.left_border + window_fil.width);
        write(outf, " ");
      end for;
    end if;
    if start_line > 1 then
      setPos(outf, start_line - 1, start_col);
      write(outf, stri);
    end if;
    if start_line + window_fil.height - 1 < height(window_fil.out_file) then
      setPos(outf, start_line + window_fil.height, start_col);
      write(outf, stri);
    end if;
  end func;


(**
 *  Clear an area of ''window_fil'' with space characters.
 *  The area is specified in (line, column) coordinates and is
 *  between the (''upper'', ''left'') and (''lower'', ''right'').
 *)
const proc: clear (inout window_file: window_fil,
    in integer: upper, in integer: left, in integer: lower, in integer: right) is func
  begin
    clear(window_fil.out_file,
        pred(window_fil.upper_border + upper),
        pred(window_fil.left_border + left),
        pred(window_fil.upper_border + lower),
        pred(window_fil.left_border + right));
    window_fil.curr_line := upper;
    window_fil.curr_column := left;
  end func;


(**
 *  Clear the area of ''window_fil'' with space characters.
 *)
const proc: clear (inout window_file: window_fil) is func
  begin
    clear(window_fil.out_file, window_fil.upper_border,
        window_fil.left_border,
        pred(window_fil.upper_border + window_fil.height),
        pred(window_fil.left_border + window_fil.width));
    window_fil.curr_line := 1;
    window_fil.curr_column := 1;
  end func;


const proc: v_scroll (inout window_file: window_fil, in integer: count) is func
  begin
    v_scroll(window_fil.out_file, window_fil.upper_border,
        window_fil.left_border,
        window_fil.upper_border + window_fil.height - 1,
        window_fil.left_border + window_fil.width - 1, count);
  end func;


const proc: v_scroll (inout window_file: window_fil,
    in integer: upper, in integer: left, in integer: lower, in integer: right, in integer: count) is func
  begin
    v_scroll(window_fil.out_file,
        pred(window_fil.upper_border + upper),
        pred(window_fil.left_border + left),
        pred(window_fil.upper_border + lower),
        pred(window_fil.left_border + right),
        count);
  end func;


const proc: h_scroll (inout window_file: window_fil, in integer: count) is func
  begin
    h_scroll(window_fil.out_file, window_fil.upper_border,
        window_fil.left_border,
        window_fil.upper_border + window_fil.height - 1,
        window_fil.left_border + window_fil.width - 1, count);
  end func;


const proc: h_scroll (inout window_file: window_fil,
    in integer: upper, in integer: left, in integer: lower, in integer: right, in integer: count) is func
  begin
    h_scroll(window_fil.out_file,
        pred(window_fil.upper_border + upper),
        pred(window_fil.left_border + left),
        pred(window_fil.upper_border + lower),
        pred(window_fil.left_border + right),
        count);
  end func;


const proc: color (inout window_file: window_fil, in color: col) is func
  begin
    color(window_fil.out_file, col);
  end func;


const proc: color (inout window_file: window_fil, in color: col1, in color: col2) is func
  begin
    color(window_fil.out_file, col1, col2);
  end func;


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


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


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


(**
 *  Write a string to the current position of ''window_fil''.
 *)
const proc: write (inout window_file: window_fil, in string: stri) is func
  begin
    if window_fil.curr_line >= 1 and
        window_fil.curr_line <= window_fil.height and
        window_fil.curr_column <= window_fil.width then
      if window_fil.curr_column >= 1 then
        setPos(window_fil.out_file,
            pred(window_fil.upper_border + window_fil.curr_line),
            pred(window_fil.left_border + window_fil.curr_column));
        write(window_fil.out_file,
            stri[.. succ(window_fil.width - window_fil.curr_column)]);
      elsif length(stri) >= 2 - window_fil.curr_column then
        setPos(window_fil.out_file,
            pred(window_fil.upper_border + window_fil.curr_line),
            window_fil.left_border);
        write(window_fil.out_file,
            stri[2 - window_fil.curr_column ..
            succ(window_fil.width - window_fil.curr_column)]);
      end if;
    end if;
    window_fil.curr_column +:= length(stri);
  end func;


(**
 *  Write end-of-line to a ''window_fil''.
 *  Set the current position to the beginning of the next line.
 *)
const proc: writeln (inout window_file: window_fil) is func
  begin
    if window_fil.curr_line = window_fil.height then
      v_scroll(window_fil.out_file, window_fil.upper_border,
          window_fil.left_border,
          window_fil.upper_border + window_fil.height - 1,
          window_fil.left_border + window_fil.width - 1, 1);
    else
      incr(window_fil.curr_line);
    end if;
    window_fil.curr_column := 1;
  end func;


const proc: moveLeft (inout window_file: window_fil, in string: stri) is func
  begin
    window_fil.curr_column -:= length(stri);
  end func;


const proc: erase (inout window_file: window_fil, in string: stri) is func
  begin
    if window_fil.curr_line >= 1 and
        window_fil.curr_line <= window_fil.height and
        window_fil.curr_column <= window_fil.width then
      if window_fil.curr_column >= 1 then
        setPos(window_fil.out_file,
            pred(window_fil.upper_border + window_fil.curr_line),
            pred(window_fil.left_border + window_fil.curr_column));
        erase(window_fil.out_file,
            stri[.. succ(window_fil.width - window_fil.curr_column)]);
      elsif length(stri) >= 2 - window_fil.curr_column then
        setPos(window_fil.out_file,
            pred(window_fil.upper_border + window_fil.curr_line),
            window_fil.left_border);
        erase(window_fil.out_file,
            stri[2 - window_fil.curr_column ..
            succ(window_fil.width - window_fil.curr_column)]);
      end if;
    end if;
    window_fil.curr_column +:= length(stri);
  end func;


const proc: backSpace (inout window_file: window_fil) is func
  begin
    if window_fil.curr_column > 1 then
      decr(window_fil.curr_column);
      if window_fil.curr_column <= window_fil.width then
        setPos(window_fil.out_file,
            window_fil.upper_border + window_fil.curr_line - 1,
            window_fil.left_border + window_fil.curr_column - 1);
        write(window_fil.out_file, " ");
        setPos(window_fil.out_file,
            window_fil.upper_border + window_fil.curr_line - 1,
            window_fil.left_border + window_fil.curr_column - 1);
      end if;
    end if;
  end func;


const proc: cursorOn (inout window_file: window_fil, in char: cursorChar) is func
  begin
    if window_fil.curr_line >= 1 and
        window_fil.curr_line <= window_fil.height and
        window_fil.curr_column >= 1 and
        window_fil.curr_column <= window_fil.width then
      setPos(window_fil.out_file,
          window_fil.upper_border + window_fil.curr_line - 1,
          window_fil.left_border + window_fil.curr_column - 1);
      cursorOn(window_fil.out_file, cursorChar);
    end if;
  end func;


const proc: cursorOff (inout window_file: window_fil, in char: cursorChar) is func
  begin
    if window_fil.curr_line >= 1 and
        window_fil.curr_line <= window_fil.height and
        window_fil.curr_column >= 1 and
        window_fil.curr_column <= window_fil.width then
      setPos(window_fil.out_file,
          window_fil.upper_border + window_fil.curr_line - 1,
          window_fil.left_border + window_fil.curr_column - 1);
      cursorOff(window_fil.out_file, cursorChar);
    end if;
  end func;