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

IMPLEMENTATION MODULE Deletions ;
(* Handles storage and recovery of deleted text *)

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

FROM Universe IMPORT BYTE , Debugging , DebugFlags ;
IMPORT Errors ;
FROM Memory IMPORT DynamicArray , ArrayByteP ,
		   NewDynamicArray , MemoryType , MemoryDirection ;
IMPORT Buffers ;
IMPORT Fast ;
IMPORT Windows ;

IMPORT Debug ;

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

CONST
   DefaultBufferSize	   = 1000 ;
   MinimumBufferSize	   = 1000 ;

(* Special bytes are inserted into the deleted text to separate it into
   separate chuncks and indicate the direction on motion during deletion.
   Undeleting a LeftToRight item should result in the new cursor moving
   to the right, a RightToLeft item to the left.
 *)
   MarkerBase		   = 0FBH ;
   MarkerSavedArea	   = 0FBH ;
   MarkerEndBuffer	   = 0FCH ;
   MarkerLiteralNextByte   = 0FDH ;
   MarkerLeftToRight	   = 0FEH ; 
   MarkerRightToLeft	   = 0FFH ; 
   
(*----------------------------------------------------------------------*)

VAR
   CurrentItem		   : CARDINAL ;
   CircularBuffer	   : DynamicArray ;
   EveryDeletionWanted	   : BOOLEAN ;
   PendingSavedArea	   : RECORD
      Valid		      : BOOLEAN ;
      Direction 	      : DeletionDirection ;
      Buffer		      : Buffers.BufferP ;
			     END ;

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

(* :*
PROCEDURE DescribeStack ;
VAR
   Item 			: CARDINAL ;
   Byte 			: BYTE ;
   I				: CARDINAL ;
BEGIN
   Debug.WriteS( "Deleted Area - Valid=" ) ;
   Debug.WriteB( PendingSavedArea.Valid ) ;
   IF PendingSavedArea.Valid AND ( PendingSavedArea.Buffer <> NIL ) THEN
      Debug.WriteS( " Buffer^.Valid=" ) ;
      Debug.WriteB( PendingSavedArea.Buffer^.Deleted.Valid ) ;
      Debug.WriteS( " [ " ) ;
      Debug.WriteC( PendingSavedArea.Buffer^.Deleted.Area.Start ) ;
      Debug.WriteS( ".." ) ;
      Debug.WriteC( PendingSavedArea.Buffer^.Deleted.Area.End ) ;
      Debug.WriteS( " ]" ) ;
   END (* if *) ;
   Debug.WriteS( " Direction=" ) ;
   IF PendingSavedArea.Direction = DeletionLeftToRight THEN
      Debug.WriteS( "LeftToRight, Buffer=" ) ;
   ELSIF PendingSavedArea.Direction = DeletionRightToLeft THEN
      Debug.WriteS( "RightToLeft, Buffer=" ) ;
   ELSE
      Debug.WriteS( "??? , Buffer=" ) ;
   END (* if *) ;
   Debug.WritePointer( PendingSavedArea.Buffer ) ;
   Debug.Writeln ;
   Item := CurrentItem ;
   Debug.WriteS( "Deletions.Stack : CurrentItem = " ) ;
   Debug.WriteC( Item ) ;
   Debug.Writeln ;
   FOR I := 1 TO 20 DO
      Byte := CircularBuffer.Data^[ Item ] ;
      IF Item = 0 THEN
	 Item := CircularBuffer.Size ;
      END (* if *) ;
      DEC( Item ) ;
      Debug.WriteHex( Byte , 2 ) ;
      Debug.Wrch( ' ' ) ;
   END (* for *) ;
   Debug.Writeln ;
END DescribeStack ;
*: *)

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

PROCEDURE PopByte( VAR Byte : BYTE ) : BOOLEAN ;
(* Attempts to obtain the next byte from the current item in the
   deleted buffer.
*)
VAR
   OldItem		   : CARDINAL ;
BEGIN

(* :*
Debug.WriteS( "PopByte*N" ) ;
DescribeStack ;
*: *)

   IF PendingSavedArea.Valid THEN
      WITH PendingSavedArea.Buffer^ DO
	 IF Deleted.Area.Start < Deleted.Area.End THEN
	    IF PendingSavedArea.Direction = DeletionLeftToRight THEN
	       DEC( Deleted.Area.End ) ;
	       Byte := Array.Data^[ Deleted.Area.End ] ;
	    ELSIF PendingSavedArea.Direction = DeletionRightToLeft THEN
	       Byte := Array.Data^[ Deleted.Area.Start ] ;
	       INC( Deleted.Area.Start ) ;
	    ELSE
	       Errors.Panic( "Deletions.PopByte : Pending area direction" ) ;
	    END (* if *) ;

(* :*
Debug.WriteS( "PopedByte = " ) ;
Debug.WriteChar( Byte ) ;
Debug.Writeln ;
*: *)

	    RETURN TRUE ;
	 ELSE
	    PendingSavedArea.Valid := FALSE ;
	    Deleted.Valid := FALSE ;
	    RETURN FALSE ;
	 END (* if *) ;
      END (* with *) ;
   ELSE
      OldItem := CurrentItem ;
      Byte := CircularBuffer.Data^[ CurrentItem ] ;
      IF CurrentItem = 0 THEN
	 CurrentItem := CircularBuffer.Size ;
      END (* if *) ;
      DEC( CurrentItem ) ;
      IF Byte < CHAR( MarkerBase ) THEN
	 CircularBuffer.Data^[ OldItem ] := CHAR( MarkerEndBuffer ) ;
	 RETURN TRUE ;
      END (* if *) ;
      IF Byte = CHAR( MarkerLiteralNextByte ) THEN
	 CircularBuffer.Data^[ OldItem ] := CHAR( MarkerEndBuffer ) ;
	 RETURN PopByte( Byte ) ;
      END (* if *) ;	     
      CurrentItem := OldItem ;
      RETURN FALSE ;
   END (* if *) ;
END PopByte ;

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

PROCEDURE PopDirection( VAR Direction : DeletionDirection ) : BOOLEAN ;
VAR
   Marker	     : BYTE ;

   PROCEDURE PopSavedAreaHeader() : BOOLEAN ;
   VAR
      Identity			: BYTE ;
      Window			: Windows.WindowP ;
   BEGIN
      Identity := CircularBuffer.Data^[ CurrentItem ] ;
      IF CurrentItem = 0 THEN
	 CurrentItem := CircularBuffer.Size ;
      END (* if *) ;
      DEC( CurrentItem ) ;
      Window := Windows.Find( CARDINAL( Identity ) ) ;
      IF Window = NIL THEN
	 RETURN FALSE ;
      ELSE
	 PendingSavedArea.Buffer := Window^.Buffer ;
      END (* if *) ;
      IF PopDirection( Direction ) THEN
	 PendingSavedArea.Direction := Direction ;
	 PendingSavedArea.Buffer^.Deleted.Valid := TRUE ;
	 PendingSavedArea.Valid := TRUE ;
	 RETURN TRUE ;
      ELSE
	 PendingSavedArea.Valid := FALSE ;
	 RETURN FALSE ;
      END (* if *) ;
   END PopSavedAreaHeader ;

BEGIN

(* :*
Debug.WriteS( "PopDirection*N" ) ;
DescribeStack ;
*: *)

   Marker := CircularBuffer.Data^[ CurrentItem ] ;
   IF CurrentItem = 0 THEN
      CurrentItem := CircularBuffer.Size ;
   END (* if *) ;
   DEC( CurrentItem ) ;
   CASE CARDINAL( Marker ) OF
      MarkerLeftToRight :
	 Direction := DeletionLeftToRight ;
	 RETURN TRUE ;
   |  MarkerRightToLeft :
	 Direction := DeletionRightToLeft ;
	 RETURN TRUE ;
   | MarkerSavedArea :
	 IF PopSavedAreaHeader() THEN
	    RETURN TRUE ;
	 ELSE
	    Errors.Report( INTEGER( Errors.DeletedAreaNoLongerAvailable ) ) ;
	    RETURN FALSE ;
	 END (* if *) ;
   | MarkerEndBuffer :
      (* RETURN FALSE *) ;
   ELSE
      Errors.Panic( "Deleted buffer bad item" ) ;
   END (* if *) ;
   RETURN FALSE ;
END PopDirection ;

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

PROCEDURE Push( From , To : CARDINAL ;
		Array : DynamicArray ;
		Direction : DeletionDirection ) ;
(* Puts the string on the deleted item stack. *)

VAR
   Byte 		: BYTE ;


   PROCEDURE PushByte( At : CARDINAL ) ;
   VAR
      Byte		   : BYTE ;
   BEGIN
      Byte := Array.Data^[ At ] ;
      IF Byte >= CHAR( MarkerBase ) THEN
	 CircularBuffer.Data^[ CurrentItem  ] := CHAR( MarkerLiteralNextByte );
	 INC( CurrentItem ) ;
	 IF CurrentItem = CircularBuffer.Size THEN
	    CurrentItem := 0 ;
	 END (* if *) ;
      END (* if *) ;
      CircularBuffer.Data^[ CurrentItem  ] := Byte ;
      INC( CurrentItem ) ;
      IF CurrentItem = CircularBuffer.Size THEN
	 CurrentItem := 0 ;
      END (* if *) ;
   END PushByte ;

BEGIN
   INC( CurrentItem ) ;
   IF CurrentItem = CircularBuffer.Size THEN
      CurrentItem := 0 ;
   END (* if *) ;
   IF Direction = DeletionLeftToRight THEN
      WHILE From < To DO
	 PushByte( From ) ;
	 INC( From ) ;
      END (* while *) ;
      CircularBuffer.Data^[ CurrentItem  ] := CHAR( MarkerLeftToRight ) ;
   ELSE
      WHILE From < To DO
	 PushByte( To - 1 ) ;
	 DEC( To ) ;
      END (* while *) ;
      CircularBuffer.Data^[ CurrentItem  ] := CHAR( MarkerRightToLeft ) ;
   END (* if *) ;
END Push ;

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

PROCEDURE SaveArea( Area : Buffers.AreaR ; Buffer : Buffers.BufferP ;
		    Direction : DeletionDirection ) : BOOLEAN ;
(* Puts the area into the end of the buffer. *)
VAR
   Length			    : CARDINAL ;
   MovedDisplacement		    : CARDINAL ;
BEGIN
   Length := Area.End - Area.Start ;
   WITH Buffer^ DO
      IF Length > ( Array.Size - After.End ) THEN
	 IF Length > ( ( Array.Size - After.End ) +
		       ( After.Start - Before.End ) ) THEN
	    Errors.Report( INTEGER(
			      Errors.AreaIsTooLargeToSaveInDeletionsArea ) ) ;
	    RETURN FALSE ;
	 END (* if *) ;
	 MovedDisplacement := Length - ( Array.Size - After.End ) ;

(* :*
IF DebugMarkerF IN Debugging THEN
Debug.WriteS( "Deletions.SaveArea : Shuffle Move( , [ " ) ;
Debug.WriteC( After.Start ) ;
Debug.WriteS( ".." ) ;
Debug.WriteC( After.End ) ;
Debug.WriteS( " ] , to " ) ;
Debug.WriteC( After.Start - MovedDisplacement ) ;
Debug.WriteS( " )*N" ) ;
END (* Debugging *) ;
*: *)

	 Fast.Move( Buffer^.Array ,
		    After.End - After.Start ,
		    After.Start ,
		    After.Start - MovedDisplacement ) ;
	 Buffers.ScanAreaForMarkers( Buffer ,
				     Buffer^.After ,
				     Buffers.MoveMarker ,
				     -INTEGER( MovedDisplacement ) ) ;
	 IF Area.Start >= After.Start THEN
	    DEC( Area.Start , MovedDisplacement ) ;
	    DEC( Area.End , MovedDisplacement ) ;
	 END (* if *) ;
	 DEC( After.Start , MovedDisplacement ) ;
	 DEC( After.End , MovedDisplacement ) ;
      END (* if *) ;

(* :*
IF DebugMarkerF IN Debugging THEN
Debug.WriteS( "Deletions.SaveArea : Move( , [ " ) ;
Debug.WriteC( Area.Start ) ;
Debug.WriteS( ".." ) ;
Debug.WriteC( Area.End ) ;
Debug.WriteS( " ] , to " ) ;
Debug.WriteC( After.End ) ;
Debug.WriteS( " )*N" ) ;
END (* Debugging *) ;
*: *)

      Fast.Move( Buffer^.Array , Length , Area.Start , After.End ) ;
      Deleted.Area.Start := After.End ;
      Deleted.Area.End := After.End + Length ;
      Deleted.Valid := TRUE ;
   END (* with *) ;
   INC( CurrentItem ) ;
   IF CurrentItem = CircularBuffer.Size THEN
      CurrentItem := 0 ;
   END (* if *) ;
   IF Direction = DeletionLeftToRight THEN
      CircularBuffer.Data^[ CurrentItem  ] := CHAR( MarkerLeftToRight ) ;
   ELSE
      CircularBuffer.Data^[ CurrentItem  ] := CHAR( MarkerRightToLeft ) ;
   END (* if *) ;
   INC( CurrentItem ) ;
   IF CurrentItem = CircularBuffer.Size THEN
      CurrentItem := 0 ;
   END (* if *) ;
   CircularBuffer.Data^[ CurrentItem  ] := CHAR( Windows.Selected^.Identity ) ;
   INC( CurrentItem ) ;
   IF CurrentItem = CircularBuffer.Size THEN
      CurrentItem := 0 ;
   END (* if *) ;
   CircularBuffer.Data^[ CurrentItem  ] := CHAR( MarkerSavedArea ) ;

(* :*
IF DebugMarkerF IN Debugging THEN
Debug.WriteS( "SaveArea, End*N" ) ;
DescribeStack ;
END (* Debugging *) ;
*: *)

   RETURN TRUE ;
END SaveArea ;

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

PROCEDURE Initialise ;
VAR
   I		     : CARDINAL ;
BEGIN
   CurrentItem := 0 ;
   IF NewDynamicArray( CircularBuffer , DefaultBufferSize , HeapMemory ) THEN
      FOR I := 0 TO CircularBuffer.Size - 1 DO
	 CircularBuffer.Data^[ I ] := CHAR( MarkerEndBuffer ) ;
      END (* for *) ;
   ELSE
      Errors.Panic( "Unable to initialise Deleted buffer" ) ;
   END (* if *) ;
   EveryDeletionWanted := FALSE ;
   PendingSavedArea.Valid := FALSE ;
END Initialise ;

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

PROCEDURE Terminate ;
BEGIN
END Terminate ;

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

BEGIN
END Deletions.

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

