(*------------------------------------------------------------------------*)

IMPLEMENTATION MODULE Screen;

(*------------------------------------------------------------------------*)

FROM Windows IMPORT Vertical, Horizontal, PointR;
FROM Memory IMPORT DynamicArray, MemoryType, NewDynamicArray;
FROM Buffers IMPORT BufferFlags;
FROM Universe IMPORT BYTE;
FROM Universe IMPORT Debugging, DebugFlags;
FROM Display IMPORT DisplayCursor, CurrentWindow;
IMPORT Windows, Screen, Interface, Vdu, Errors, Fast, Debug;
IMPORT Characters;

FROM GlobalString IMPORT GetGlobalString;
FROM Convert IMPORT StringToCardinal;

(*------------------------------------------------------------------------*)

VAR
   BlankLine		       : DynamicArray;
   ScreenCursor 	       : PointR;
   CursorEnabled	       : BOOLEAN;
   NoScrollBottomCornerActive  : BOOLEAN;

(*------------------------------------------------------------------------*)

PROCEDURE UpdateCh (Ch: BYTE);

   BEGIN
      WITH Image [DisplayCursor.Y] DO
         IF CARDINAL (Priorities.Data^ [DisplayCursor.X]) # CurrentWindow^.Priority THEN
	    RETURN
         END (* if *);
         IF Chars.Data^ [DisplayCursor.X] = Ch THEN

(* *:
IF Ch = BYTE (Characters.UnknownChar) THEN
   Debug.WriteS ("Screen.UpdateCh : UnknownCh");
   Errors.Panic ("Screen.UpdateCh : Unknownch")
END (* if *);
*: *)

	    RETURN
         ELSE
	    IF CursorEnabled THEN
	       Vdu.DisableCursor;
	       CursorEnabled := FALSE
 	    END (* if *);
            IF (ScreenCursor.X = DisplayCursor.X) AND (ScreenCursor.Y = DisplayCursor.Y) THEN
            ELSE GotoXY (DisplayCursor.X, DisplayCursor.Y)
            END (* if *);
	    IF DisplayCursor.X = Screen.LastColumn THEN
	       IF DisplayCursor.Y = Screen.BottomRow THEN
	          (* next write will scroll, we need to try to bodge the
                  MOS to avoid this  *)

(* :*
IF DebugDisplayF IN Debugging THEN
Debug.WriteS( "Screen.UpdateCh : Activating no scroll bottom corner" ) ;
END (* if debugging *) ;
*: *)

	          IF Vdu.ActivateNoScrollBottomCorner () THEN
		     Interface.Wrch (Ch);
		     Vdu.DisableNoScrollBottomCorner
	          END (* if *)
	       ELSE
	          Interface.Wrch (Ch);
	          Interface.Wrch (BYTE (Vdu.BackSpace))
	       END (* if *)
	    ELSE
	       Interface.Wrch (Ch);
	       INC (ScreenCursor.X)
	    END (* if *);
            Chars.Data^ [DisplayCursor.X] := Ch
         END (* if *)
      END (* with *)
   END UpdateCh;

(*------------------------------------------------------------------------*)

PROCEDURE InverseCh (Ch: BYTE);

   (* Outputs character in inverse video.
   The screen image will never match at this position ! *)

   BEGIN
      WITH Image [DisplayCursor.Y] DO
         IF CARDINAL (Priorities.Data^ [DisplayCursor.X]) # CurrentWindow^.Priority THEN
            RETURN
         END (* if *);
         IF CursorEnabled THEN
            Vdu.DisableCursor;
            CursorEnabled := FALSE
         END (* if *);
         IF (ScreenCursor.X = DisplayCursor.X) AND (ScreenCursor.Y = DisplayCursor.Y) THEN
         ELSE GotoXY (DisplayCursor.X, DisplayCursor.Y)
         END (* if *);
         IF DisplayCursor.X = Screen.LastColumn THEN
            IF DisplayCursor.Y = Screen.BottomRow THEN
   	       (* next write will scroll, we need to try to bodge the MOS to
               avoid this *)

(* :*
IF DebugDisplayF IN Debugging THEN
Debug.WriteS( "Screen.InverseCh : Activating no scroll bottom corner" ) ;
END (* if debugging *) ;
*: *)

               IF Vdu.ActivateNoScrollBottomCorner () THEN
                  Interface.Wrch (BYTE (07FH));
                  Interface.Wrch (Ch);
	          Vdu.DisableNoScrollBottomCorner
	       END (* if *)
            ELSE
               Interface.Wrch (BYTE (07FH));
               Interface.Wrch (Ch);
	       Interface.Wrch (BYTE (Vdu.BackSpace))
            END (* if *)
         ELSE
            Interface.Wrch (BYTE (07FH));
            Interface.Wrch (Ch);
            INC (ScreenCursor.X)
         END (* if *);
            Chars.Data^ [DisplayCursor.X] := BYTE (Characters.UnknownChar)
         END (* with *)
      END InverseCh;

(*------------------------------------------------------------------------*)


PROCEDURE GotoXY (X: Horizontal; Y: Vertical);

   CONST TunedMaxHorizontal = 2;

   VAR
      PreviousCursor  : PointR;
      DeltaX,
      I	              : INTEGER;

   BEGIN
      PreviousCursor := ScreenCursor;
      ScreenCursor.X := X;
      ScreenCursor.Y := Y;
      IF ScreenCursor.Y = PreviousCursor.Y THEN
         DeltaX := INTEGER (ScreenCursor.X - PreviousCursor.X);
         CASE DeltaX OF
            -TunedMaxHorizontal..-1:

	    FOR I := -1 TO DeltaX BY -1 DO
	       Interface.Wrch (BYTE (Vdu.BackSpace))
	    END (* for *)

        |  0: (* Do nothing - we're there already *)

        | 1..TunedMaxHorizontal:

	    FOR I := 1 TO DeltaX DO
	      Interface.Wrch (BYTE (Vdu.CtrlI))
	    END (* for *)

        ELSE Vdu.GotoXY (X, Y)

        END (* case *)
      ELSE Vdu.GotoXY (X, Y)
      END (* if *)
   END GotoXY;

(*------------------------------------------------------------------------*)

PROCEDURE BlankPartOfLine (Left, Right: Horizontal);

   CONST TunedFudgeFactor  = 10;

   VAR
      Junk 	: BOOLEAN;
      I		: Horizontal;
      X		: Horizontal;
      ThisLine	: LineRecord;

   BEGIN

(* :*
IF DisplayCursor.Y = Screen.BottomRow THEN
   Debug.WriteS ("BlankPartOfLine (");
   Debug.WriteC (Left);
   Debug.WriteS (", ");
   Debug.WriteC (Right);
   Debug.WriteS (")*N");
END (* if *);
*: *)

      WITH CurrentWindow^ DO
         DisplayCursor.X := Left;

         ThisLine := Screen.Image [DisplayCursor.Y];
         LOOP
 	    CASE Fast.WhereNotBlank (ThisLine.Chars,
			   (* VAR *) DisplayCursor.X,
			 	     Right,
				     ThisLine.Priorities,
				     CurrentWindow^.Priority) OF
	       Fast.DifferentHere:

	       (* A difference exists, therefore wipe the gap *)

            |  Fast.DifferentWindowHere:

	       (* This position is owned by a different window ! *)

	       WITH DisplayCursor DO
	         IF X < Right THEN BlankPartOfLine (X + 1, Right) END
	       END (* with *);
	       RETURN

	    ELSE
	    
	       (* No more differences *)

	       RETURN (* The rest of the line is already blank *)

	    END (* case *);

	    IF (DisplayCursor.X + TunedFudgeFactor) < Right THEN

	       X := DisplayCursor.X;
	       CASE Fast.WhereDifferentWindow ((* VAR *) X,
					       Right,
					       ThisLine.Priorities,
					       CurrentWindow^.Priority) OF
	          Fast.DifferentWindowHere:

	          (* A difference window exists , be carefull !!! *)

	          IF X > Left THEN BlankPartOfLine (Left, X - 1) END;
	          IF X < Right THEN BlankPartOfLine (X + 1, Right) END;
	          RETURN

	       ELSE

	       (* we can wipe the window to the edge of screen *)

	       END (* case *);

	       IF (DisplayCursor.Y = Screen.BottomRow) AND (Right = Screen.LastColumn) THEN

	          (* Scrolling in bottom corner problem *)

(* Bugfix: DisplayCursor.X below was X *)

	          BlankPartOfLine (DisplayCursor.X, Right - 1);
	          DisplayCursor.X := Screen.LastColumn;
	          UpdateCh (BYTE (' '));
	          RETURN
	       END (* if *);

	       Fast.Copy (Right - Left + 1, BlankLine, Left, ThisLine.Chars, Left);
	       IF CursorEnabled THEN
	          Vdu.DisableCursor;
	          CursorEnabled := FALSE
	       END (* if *);
	       Vdu.ClearWindow (Left, DisplayCursor.Y, Right, DisplayCursor.Y);
	       ScreenCursor.X := 0;
	       ScreenCursor.Y := 0;
	       RETURN

	    ELSE

	       UpdateCh (BYTE (' '));
	       IF DisplayCursor.X < Right THEN INC (DisplayCursor.X)
	       ELSE RETURN
	       END (* if *)

   	    END (* if *)
         END (* loop *)
      END (* with *)
   END BlankPartOfLine;

(*------------------------------------------------------------------------*)


PROCEDURE EnableCursor;

   (* Displays the hardware cursor at Windows.Selected^.Cursor.
   The cursor remains enabled until something else is displayed *)

   BEGIN
      WITH Windows.Selected^ DO
         IF Windows.NoCursorF IN Status THEN
	    IF CursorEnabled THEN
	       Vdu.DisableCursor;
	       CursorEnabled := FALSE
	    END (* if *)
         ELSE
	    GotoXY (Cursor.Position.X, Cursor.Position.Y);
	    IF NOT CursorEnabled THEN
	       Vdu.EnableCursor (ModifiedF IN Buffer^.Status);
	       CursorEnabled := TRUE;
	    END (* if *)
         END (* if *)
      END (* with *)
   END EnableCursor;

(*------------------------------------------------------------------------*)

PROCEDURE Initialise;

   PROCEDURE AcceptableMode (mode: CARDINAL): BOOLEAN;
   
      BEGIN
         RETURN ((mode = 0) OR (mode = 128) OR (mode = 3) OR (mode = 131))
      END AcceptableMode;

   VAR
      editMode  : CARDINAL;
      mode      : CARDINAL;
      buffer	: ARRAY [0..20] OF CHAR;
      len	: CARDINAL;
      X		: Horizontal;
      Y		: Vertical;

   BEGIN
      LOOP (* exception *)

      IF AcceptableMode (Vdu.OriginalMode) THEN editMode := Vdu.OriginalMode
      ELSE editMode := 128
      END (* if *);
      IF GetGlobalString (buffer, len, "EDIT$ScreenMode") >= 0 THEN
         IF StringToCardinal (mode, buffer) >= 0 THEN
            IF AcceptableMode (mode) THEN editMode := mode END;
         END (* if *);
      END (* if *);

      Vdu.Mode (editMode);
      
      IF NOT NewDynamicArray (BlankLine, 80, HeapMemory) THEN EXIT END;
      FOR X := 0 TO LastColumn DO
         BlankLine.Data^ [X] := BYTE (' ');
      END (* for *) ;

      FOR Y := 0 TO 31 DO
         IF NOT NewDynamicArray (Image [Y].Chars, 80, HeapMemory) THEN EXIT END;
         IF NOT NewDynamicArray (Image [Y].Priorities, 80, HeapMemory) THEN EXIT END;
         IF Y = 0 THEN
	    FOR X := 0 TO 79 DO
	       Image [Y].Priorities.Data^ [X] := BYTE (0);
	       Image [Y].Chars.Data^ [X] := BYTE (' ');
	    END (* for *);
         ELSE
	    Fast.Copy (80, Image [Y - 1].Chars, 0, Image [Y].Chars, 0);
	    Fast.Copy (80, Image [Y - 1].Priorities, 0, Image [Y].Priorities, 0);
         END (* if *);
      END (* for *);

      BottomRow := Vertical (CARDINAL (Interface.TkByte (160, 9, 0)) MOD 256);
      Height := BottomRow + 1;
      ScreenCursor.X := 0;
      ScreenCursor.Y := 0;
      Vdu.ClearScreen;
      CursorEnabled := TRUE;
      NoScrollBottomCornerActive := FALSE;
      
      RETURN
      
      END (* loop *);
      
      Errors.Panic ("Screen: insufficient memory to create screen image*N");
      
   END Initialise;

(*------------------------------------------------------------------------*)

PROCEDURE Terminate;

   BEGIN
   END Terminate;

(*------------------------------------------------------------------------*)

END Screen.

