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

IMPLEMENTATION MODULE DisplayPosition;

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

FROM Universe IMPORT BYTE, DebugFlags, Debugging, EndOfLineCh, WindowI,
		     MaxWindows, MaxHorizontal, MaxVertical;
FROM Windows IMPORT Vertical, Horizontal, WindowP, DisplaySegmentR,
		    WindowStatusF;
FROM Buffers IMPORT MarkerP, MarkerAt, MarkerWithin, AreaR;
IMPORT Display, Windows, Fast, Errors;

IMPORT Debug;

(* :*
IMPORT Describe;
*: *)

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

PROCEDURE Move (DeltaX, DeltaY: INTEGER);

   VAR bodgeRequired: BOOLEAN;

   BEGIN
      WITH Display.CurrentWindow^ DO

         IF DisplayCursorValidF IN Status THEN

            bodgeRequired := (Cursor.Position.Y = Edge.Top) AND (DeltaY > 0);

 	    IF DeltaX > 0 THEN
	       IF DisplayFoldedF IN Status THEN
	          SetUnknown;
	          RETURN;
	       ELSE
	          IF NOT Cursor.OnText THEN
		     INC (Cursor.PartialColumn, DeltaX);
	          END (* if *) ;
	          SetCursorXPosition (Display.CurrentWindow, INTEGER (Cursor.Column) + DeltaX);
(* Hardly in the spirit of things, but ... *)
DisplayAreas [Cursor.Position.Y].Valid := FALSE;
	       END (* if *);
	    END (* if *);

	    IF DeltaX < 0 THEN
	       IF Cursor.Column > 0 THEN
	          IF NOT Cursor.OnText THEN
		     IF -DeltaX < INTEGER (Cursor.PartialColumn) THEN
		        DEC (Cursor.PartialColumn, -DeltaX);
		     ELSE
		        Cursor.OnText := TRUE;
		        Cursor.PartialColumn := 0;
		     END (* if *);
	          END (* if *);
	          SetCursorXPosition (Display.CurrentWindow, INTEGER (Cursor.Column) + DeltaX);
(* Hardly in the spirit of things, but ... *)
DisplayAreas [Cursor.Position.Y].Valid := FALSE;
	       ELSE
	          SetUnknown;
	          RETURN;
	       END (* if *);
	       INC (DeltaX);
	    END (* if *);

	    IF DeltaY > 0 THEN
	       IF Cursor.Position.Y < Edge.Bottom THEN
	          SetInvalid (Display.CurrentWindow, Cursor.Position.Y + 1, Edge.Bottom);
	       END (* if *);
	       WHILE DeltaY > 0 DO
	          IF Cursor.Position.Y < Margins.Current.Bottom THEN
		     ExtendRow (Display.CurrentWindow, Cursor.Position.Y);
		     INC (Cursor.Position.Y);
	          ELSE
		     SetUnknown;
		     RETURN;
	          END (* if *);
	          DEC (DeltaY);
	       END (* while *);
	    END (* if *);

	    WHILE DeltaY < 0 DO
	       IF Cursor.Position.Y > Margins.Current.Top THEN
	          DisplayAreas [Cursor.Position.Y].Valid := FALSE;
	          DEC (Cursor.Position.Y);
	       ELSE
	          SetUnknown;
	          RETURN;
	       END (* if *);
	       INC (DeltaY);
	    END (* while *);

            (* Bodge fix for bug whereby NewLine on 1st line in a window
            gave incorrect screen update *)

            IF bodgeRequired THEN
               FindCursorPosition (Display.CurrentWindow);
            END (* if *);

	    ValidateRow (Display.CurrentWindow, Cursor.Position.Y);

         END (* if *);

      END (* with *);
   END Move;

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

PROCEDURE ExtendRow (Window: WindowP; Y: Vertical);

   (* The Start of the display Row is correct, but it may have been truncated
   because it was the cursored line *)

   VAR Area: AreaR;

   BEGIN
      WITH Window^ DO

         IF NOT DisplayAreas [Y].Valid THEN
	    ValidateRow (Window, Y);
         END (* if *) ;

         Area.Start := DisplayAreas [Y].Area.Start;
         IF Y <= Cursor.Position.Y THEN Area.End := Buffer^.Before.End;
         ELSE Area.End := Buffer^.After.End;
         END (* if *);

         IF Fast.SkipUntilEndOfLineCh (Buffer^.Array, Area) THEN END;

         DisplayAreas [Y].Area.End := Area.Start;

      END (* with *);
   END ExtendRow;

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

PROCEDURE ValidateRow (Window: WindowP; Y: Vertical);

   VAR
      Area   : AreaR;
      P	     : CARDINAL;

   BEGIN
      WITH Window^ DO

         IF DisplayAreas [Y].Valid THEN RETURN END;

          IF Y < Cursor.Position.Y THEN

             IF NOT DisplayAreas [Y + 1].Valid THEN
	       ValidateRow (Window, Y + 1);
	       IF DisplayAreas [Y].Valid THEN RETURN END;
 	     END (* if *);

	     Area.Start := Buffer^.Before.Start;
	     Area.End := DisplayAreas [Y + 1].Area.Start;
	     IF Fast.BackUntilEndOfLineCh (Buffer^.Array, Area) THEN END;
	     MakeArea (Window, Y, Area.Start, Area.End);

          ELSIF Y > Cursor.Position.Y THEN

	     IF NOT DisplayAreas [Y - 1].Valid THEN
	        ValidateRow (Window, Y - 1);
	        IF DisplayAreas [Y].Valid THEN RETURN END
	     END (* if *);

	     Area := DisplayAreas [Y - 1].Area;
	     IF Area.End <= Buffer^.Before.End THEN
	        Area := Buffer^.After;
	        IF Fast.SkipUntilEndOfLineCh (Buffer^.Array, Area) THEN END;
	     END (* if *);
	     P := Area.Start;
	     IF Fast.SkipUntilEndOfLineCh (Buffer^.Array, Area) THEN END;
	     MakeArea (Window, Y, P, Area.Start);

          ELSE (* Y = Cursor.Position.Y *) ;

	     FindCursorPosition (Window);

          END (* if *);

      END (* with *);
   END ValidateRow;

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

PROCEDURE SetCursorXPosition (Window: WindowP; NewX: CARDINAL);

   BEGIN
      WITH Window^ DO

(* Bugfix: > below was >= *)

         IF NewX > (Width + Indentation) THEN
	    Cursor.Position.X := Edge.Right;
  	    IF Border.Right.Visible AND (NewX > (Width + Indentation)) THEN
	       INC (Cursor.Position.X);
	    END (* if *);
         ELSIF NewX >= Indentation THEN
	    Cursor.Position.X := Edge.Left + NewX - Indentation;
         ELSE
	    Cursor.Position.X := Edge.Left;
	    IF Border.Left.Visible THEN
	       DEC (Cursor.Position.X);
	    END (* if *);
         END (* if *);
         Cursor.Column := NewX;
      END (* with *);

   END SetCursorXPosition;

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

PROCEDURE SetInvalid( Window : WindowP ; From , To : Vertical ) ;
VAR
   Y				  : Vertical ;
BEGIN
   WITH Window^ DO
      FOR Y := From TO To DO
	 WITH DisplayAreas[ Y ] DO
	    Valid := FALSE ;
	    Area.Start := 0 ;
	    Area.End := 0 ;
	 END (* with *) ;
      END (* for *) ;
   END (* with *) ;
END SetInvalid ;

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

PROCEDURE SetInvalidRows( Window : WindowP ) ;
(* Inform the display package that the current display cache is invalid,
   but the current X and Y positions are to be kept if possible.
*)
BEGIN
   EXCL( Window^.Status , DisplayCompletedF ) ;
   EXCL( Window^.Status , DisplayCursorValidF ) ;
   SetInvalid( Window , Window^.Edge.Top , Window^.Edge.Bottom ) ;
END SetInvalidRows ;

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

PROCEDURE SetInvalidColumn( Window : WindowP ) ;
(* Inform the display package that the current display cache is invalid,
   but the current X position is to be completely recalculated.
*)
BEGIN
   EXCL( Window^.Status , CursorXKnownF ) ;
   Window^.Cursor.OnText := TRUE ;
   Window^.Cursor.PartialColumn := 0 ;
   SetInvalidRows( Window ) ;
END SetInvalidColumn ;

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

PROCEDURE SetUnknown ;
VAR
   Y			 : Vertical ;
BEGIN
   WITH Display.CurrentWindow^ DO
      EXCL( Status , DisplayCompletedF ) ;
      EXCL( Status , DisplayCursorValidF ) ;
      SetInvalid( Display.CurrentWindow , Edge.Top , Edge.Bottom ) ;
   END (* with *) ;END SetUnknown ;

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

PROCEDURE SetForwards( Window : WindowP ) ;
BEGIN
   EXCL( Window^.Status , DisplayUpwardsF ) ;
END SetForwards ;

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

PROCEDURE SetBackwards( Window : WindowP ) ;
BEGIN
   INCL( Window^.Status , DisplayUpwardsF ) ;
END SetBackwards ;

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

PROCEDURE FoldArea (    Window  : WindowP;
		    VAR Y       : Vertical);

   VAR
      Segment    : DisplaySegmentR;
      P	         : CARDINAL;
      LineWidth  : Horizontal;
      Marker     : MarkerP;
      FoldCount  : CARDINAL;

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

   PROCEDURE MoveUpLine (From: Vertical; NewEnd: CARDINAL): CARDINAL;

      VAR Junk: [0..1];

      BEGIN
         WITH Window^ DO
 	    IF From > Edge.Top THEN
	       Junk := MoveUpLine (From - 1, DisplayAreas [From - 1].Area.End);
 	       DisplayAreas [From - 1] := DisplayAreas [From];
	       DisplayAreas [From - 1].Area.End := NewEnd;
	       RETURN 1
	    END (* if *);
	    RETURN 0
         END (* with *)
       END MoveUpLine;

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

   BEGIN

(* :*
Describe.Buffer (Window^.Buffer);
Describe.Window (Window);
*: *)

      WITH Window^ DO
         Indent (DisplayAreas [Y], Indentation, 0);
         Segment := DisplayAreas [Y];
         IF Segment.Area.Start > Buffer^.Before.End THEN
	    IF NOT (CursorXKnownF IN Status) THEN
	       Cursor.Position.X := Edge.Left;
	       IF Border.Left.Visible THEN
	          DEC (Cursor.Position.X)
	       END (* if *);
	       INCL (Status, CursorXKnownF)
	    END (* if *);
	    RETURN; (* Indent () has moved Area to after the hole *)
         END (* if *);
         LineWidth := 0;
         FoldCount := 0;
         P := Segment.Area.Start;
         WHILE P < Segment.Area.End DO
	    INC (LineWidth,
                 WidthDisplayCh (Buffer^.Array.Data^ [P], LineWidth, P)
                );
	    INC (P);
	    IF LineWidth >= Width THEN
	       INC (FoldCount, MoveUpLine (Y, P));
	       DEC (LineWidth, Width);
	       WITH Segment.Pending DO
	          Fold := TRUE;
	          CountValid := LineWidth > 0;
	          WidePartialCount := LineWidth;
	          IF CountValid THEN Segment.Area.Start := P - 1
	          ELSE Segment.Area.Start := P
	          END (* if *)
	       END (* with *);
	       DisplayAreas [Y] := Segment
	    END (* if *)
         END (* while *);
         IF Segment.Area.End = Buffer^.Before.End THEN
         END (* if *);
         DisplayAreas [Y] := Segment;
         IF NOT (CursorXKnownF IN Status) THEN
            (* ???????????? - Indentation on line below *)
	    Cursor.Position.X := LineWidth + Edge.Left - Indentation;
	    INCL (Status, CursorXKnownF)
         END (* if *);

(* Bugfix: prevent (crudinal) Y from going 'negative' *)

         IF FoldCount < Y THEN DEC (Y, FoldCount)
         ELSE Y := 0
         END (* if *);

      END (* with *)
   END FoldArea;

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

PROCEDURE CalculateAreaWidth (Window       : WindowP;
			      Area         : AreaR;
			      StartColumn  : CARDINAL ): CARDINAL;

   VAR
      AreaWidth	 : CARDINAL;
      ChWidth	 : CARDINAL;
      Ch	 : BYTE;
      Index	 : CARDINAL;

   BEGIN
      AreaWidth := StartColumn;
      Index := Area.Start;
      WITH Window^ DO
         WHILE Index < Area.End DO
	    Ch := Buffer^.Array.Data^ [Index];
  	    ChWidth := WidthDisplayCh (Ch, AreaWidth, Index);
	    INC (AreaWidth, ChWidth);
	    INC (Index);
         END (* while *)
      END (* with *);
      RETURN AreaWidth
   END CalculateAreaWidth;

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

PROCEDURE MakeArea (    Window  : WindowP;
		    VAR Y       : Vertical;
		        Start   : CARDINAL;
                        End     : CARDINAL );
   BEGIN
      WITH Window^ DO
         WITH DisplayAreas [Y] DO

  	    Area.Start := Start;
	    Area.End := End;
	    Pending.Fold := FALSE;
	    Pending.CountValid := FALSE;
	    Pending.WidePartialCount := 0;
	    MarkerTruncated := FALSE;
	    RealEnd := 0;
	    Valid := TRUE;

	    IF DisplayFoldedF IN Status THEN
	       FoldArea (Window, Y);
	    ELSE
	       IF NOT (CursorXKnownF IN Status) THEN
	          SetCursorXPosition (Window, CalculateAreaWidth (Window, Area, 0));
	          INCL (Status, CursorXKnownF)
	       END (* if *) ;
	       Indent (DisplayAreas [Y], Indentation, 0)
	    END (* if *)

         END (* with *)
      END (* with *);
   END MakeArea;

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

PROCEDURE FindCursorPosition (Window: WindowP);

   VAR
      Y,
      Y0,
      Y2     : Vertical;
      P	     : CARDINAL;
      OldP   : CARDINAL;
      Ch     : BYTE;

   BEGIN
      WITH Window^ DO

         IF Cursor.Position.Y < Margins.Real.Top THEN
            Cursor.Position.Y := Margins.Real.Top
         ELSIF Cursor.Position.Y > Margins.Real.Bottom THEN
 	    Cursor.Position.Y := Margins.Real.Bottom
         END (* if *);

         Y0 := Cursor.Position.Y;
         Y := Y0;
         P := Buffer^.Before.End;
         OldP := P;

         DisplayAreas [Y].Area := Buffer^.Before;

         LOOP

	    WITH DisplayAreas [Y].Area DO
	       Start := Buffer^.Before.Start;
	       End := P
	    END (* with *);

	    IF Fast.BackUntilEndOfLineCh (Buffer^.Array, DisplayAreas [Y].Area) THEN
 	       P := DisplayAreas [Y].Area.End;
	       MakeArea (Window, Y, P, OldP);
	       OldP := P;
	       DEC (P);
(* Bugfix: was = *)
 	       IF Y <= Edge.Top THEN EXIT END;
	       DEC (Y)
 	    ELSE
	       MakeArea (Window, Y, DisplayAreas [Y].Area.End, OldP);
	       FOR Y2 := 0 TO Y0 - Y DO
	          DisplayAreas [Edge.Top + Y2] := DisplayAreas [Y + Y2]
	       END (* for *);
	       DEC (Cursor.Position.Y, Y - Edge.Top);
	       EXIT
	    END (* if *)

         END (* loop *);

         EXCL (Status, Windows.DisplayCompletedF);
         INCL (Status, DisplayCursorValidF)

      END (* with *);

   END FindCursorPosition;

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

PROCEDURE Indent (VAR Segment    : DisplaySegmentR;
		      Amount     : CARDINAL;
 		      XPosition  : CARDINAL        );

   VAR
      Ch	   : BYTE;
      SizeOfToken  : CARDINAL;

   BEGIN
      IF Segment.Pending.Fold THEN RETURN END;

      WITH Display.CurrentWindow^ DO

         WHILE Amount > 0 DO

	    IF Segment.Area.Start < Segment.Area.End THEN
	       Ch := Buffer^.Array.Data^ [Segment.Area.Start];
  	       IF Ch = EndOfLineCh THEN
	          Segment.Pending.Fold := FALSE;
	          Segment.Pending.WidePartialCount := Amount;
	          Segment.Pending.CountValid := (Amount > 0);
	          RETURN;
	       ELSE
	          SizeOfToken := WidthDisplayCh (Ch,
					         XPosition,
					         Segment.Area.Start);
	       END (* if *);
	    ELSIF Segment.Area.Start > Buffer^.After.End THEN
	       Segment.Pending.Fold := FALSE;
	       Segment.Pending.CountValid := FALSE;
	       Segment.Pending.WidePartialCount := 0;
	       RETURN;
	    ELSIF Segment.Area.Start = Buffer^.After.End THEN
               (* K.D.Rautenbach presents "Magic numers" ... Twat *)
	       SizeOfToken := 1 + 11 + 1; (* "[End of Text]" *);
	    ELSIF Segment.Area.Start >= Buffer^.Before.End THEN
	       Segment.Area := Buffer^.After;
	       Indent (Segment, Amount, XPosition);
	       RETURN;
	    ELSE
	       SizeOfToken := 1;
	    END (* if *);

	    INC (XPosition, SizeOfToken);

	    IF Amount >= SizeOfToken THEN
	       DEC (Amount, SizeOfToken);
	       INC (Segment.Area.Start);
	    ELSE
	       Segment.Pending.WidePartialCount := 0;
	       Segment.Pending.CountValid := TRUE;
	       Amount := 0;
	    END (* if *);

         END (* while *);

      END (* with *);
   END Indent;

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

PROCEDURE Initialise;

   BEGIN
   END Initialise;

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

PROCEDURE Terminate;

   BEGIN
   END Terminate;

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

END DisplayPosition.

