/* -- WVBLOCK.C --------------------------------------------------
 *
 *  This file contains a collection of routines to manipulate textblocks
 *  and lines within textblocks.
 *  The routines here view lines as atomic units only (they don't
 *  look at the actual data in the lines).
 *
 *  Mark Riordan   20 September 1989.
 */

#include "windows.h"
#include "wvglob.h"
#include "winvn.h"
#include <ctype.h>

/*-- function NewBlock ------------------------------------------
 *
 *  Creates an empty, new textblock and links it into the list
 *  of blocks after a given block.
 *  After the call, both the old ("given") block and the new block
 *  are locked in memory.
 *
 *    Entry    CurBlockPtr    points to a block
 *
 *    Exit     NewBlockPtr    points to a block that has been linked
 *                            into the list just after CurBlockPtr.
 *             Returns TRUE if couldn't allocate a block, else FALSE.
 *             (I know I should fix that.)
 */
int
NewBlock (CurBlockPtr, NewBlockPtr)
     TypBlock far *CurBlockPtr, far ** NewBlockPtr;
{
  HANDLE hMyBlock;
  TypBlock far *MyBlock, far * MyNextBlock;

  hMyBlock = GlobalAlloc (GMEM_MOVEABLE, (long) (CurBlockPtr->OwnerDoc->BlockSize));
  if (hMyBlock)
    {
      MyBlock = (TypBlock far *) GlobalLock (hMyBlock);
      SetupEmptyBlock (MyBlock, hMyBlock, CurBlockPtr->hCurBlock,
		       CurBlockPtr->hNextBlock, CurBlockPtr->OwnerDoc);
      CurBlockPtr->hNextBlock = hMyBlock;

      /* Change the next block's "previous" pointer to point to us. */

      if (MyBlock->hNextBlock)
	{
	  MyNextBlock = (TypBlock far *) GlobalLock (MyBlock->hNextBlock);
	  MyNextBlock->hPrevBlock = hMyBlock;
	  GlobalUnlock (MyBlock->hNextBlock);
	}

      *NewBlockPtr = MyBlock;
    }
  else
    {
      MessageBox (CurBlockPtr->OwnerDoc->hDocWnd, "Could not allocate textblock", "Out of Memory Error", MB_OK | MB_ICONHAND);
      return (1);
    }
  return (0);
}

/*-- function SetupEmptyBlock -----------------------------------------
 *
 *  Initialize fields in a newly-allocated textblock.
 *  Set the fields to indicate an empty block.
 */
void
SetupEmptyBlock (BlockPtr, hCur, hPrev, hNext, DocPtr)
     TypBlock far *BlockPtr;
     HANDLE hCur, hPrev, hNext;
     TypDoc *DocPtr;
{
  BlockPtr->OwnerDoc = DocPtr;
  BlockPtr->hCurBlock = hCur;
  BlockPtr->hPrevBlock = hPrev;
  BlockPtr->hNextBlock = hNext;
  BlockPtr->LWAp1 = sizeof (TypBlock) + sizeof (TypLine);
  BlockPtr->NumLines = 0;
  BlockPtr->eob = END_OF_BLOCK;
#if 0
  *((int far *) ((char far *) BlockPtr + sizeof (TypBlock))) = END_OF_BLOCK;
#else
  ((TypLine far *) ((char far *) BlockPtr + sizeof (TypBlock)))->length = END_OF_BLOCK;
  ((TypLine far *) ((char far *) BlockPtr + sizeof (TypBlock)))->LineID = NextLineID++;
#endif
}

/*-- function DeleteBlock ---------------------------------------------
 *
 *  Delete a textblock from a document.
 *
 *    Entry    CurBlockPtr points to the block to delete.
 *
 *    Exit     CurBlockPtr points to the next block, if any, else the
 *                         previous block.
 *             CurLinePtr  points to the first line of the next block
 *                         if there was one, else the last line of
 *                         the previous block.
 */
BOOL
DeleteBlock (TypBlock far ** CurBlockPtr, TypLine far ** CurLinePtr)
{
  TypBlock far *MyBlockPtr = *CurBlockPtr;
  TypBlock far *BlockPtr;
  HANDLE hMyBlock = MyBlockPtr->hCurBlock, hMyPrev = MyBlockPtr->hPrevBlock, hMyNext = MyBlockPtr->hNextBlock;
  BOOL set_cur_block = FALSE;

  /* Don't delete the only block in the document. */
  if (!hMyNext && !hMyPrev)
    return (FALSE);

  /* Update the prev pointer of the next block to point to
   * the block previous to the one being deleted.
   */

  if (hMyNext)
    {
      BlockPtr = (TypBlock far *) GlobalLock (hMyNext);
      BlockPtr->hPrevBlock = hMyPrev;
      GlobalUnlock (hMyNext);

      NextLine (CurBlockPtr, CurLinePtr);
      set_cur_block = TRUE;
    }
  else
    {
      /* The block we are deleting has no next block, so it
       * must be the last in the document.  Update document pointers.
       */
      MyBlockPtr->OwnerDoc->hLastBlock = hMyPrev;
    }

  /* Update the next pointer of the previous block to point to
   * the block after the one being deleted.
   */

  if (hMyPrev)
    {
      BlockPtr = (TypBlock far *) GlobalLock (hMyPrev);
      BlockPtr->hNextBlock = hMyNext;
      GlobalUnlock (hMyPrev);

      if (!set_cur_block)
	{
	  /* There is no next block, so we want to position the
           * pointer to the end of the previous block.
           */
	  PrevLine (CurBlockPtr, CurLinePtr);
	  NextLine (CurBlockPtr, CurLinePtr);
	}
    }
  else
    {
      /* The block we are deleting has no previous block, so it
       * must be the first in the document.  Update document pointers.
       */
      MyBlockPtr->OwnerDoc->hFirstBlock = hMyNext;
    }

  GlobalFree (hMyBlock);
  return (TRUE);
}

/*-- function AddLine -------------------------------------------------
 *
 *  Add a line to a textblock.  Create a new textblock if necessary.
 *
 *    Entry    LineToAdd   points to a line to add.
 *             CurBlockPtr points to the block to which we want to add it.
 *             CurAddPtr   points to the place in the block to add the line.
 *
 *    Exit     CurBlockPtr & CurAddPtr point to right after the
 *                newly-added line.
 *
 *    Method   There are three cases:
 *             1. The new line will fit in the textblock.
 *                That's pretty easy.
 *             2. The new line won't fit, and we are at the end of
 *                the current block.
 *                I could have just treated this as I do case 3 (splitting
 *                the textblock), but that would have resulted in
 *                a lot of unnecessary internal fragmentation
 *                (i.e., wasted space inside textblocks).
 *                So, what I do is leave the contents of the old block
 *                alone and just create a new empty block, to which
 *                the line is added by a recursive call.
 *             3. The new line won't fit, and we are not at the end of
 *                the textblock.
 *                In this case, I split the textblock at the first line
 *                boundary after the split point (the split point is
 *                an attribute of the document--usually about 2/3 of the
 *                way through the textblock), creating two semi-full textblocks
 *                where there was one full one before.
 *                The routine then recursively calls itself.
 */
int
AddLine (LineToAdd, CurBlockPtr, CurAddPtr)
     TypLine *LineToAdd;
     TypBlock far **CurBlockPtr;
     TypLine far **CurAddPtr;
{
  TypBlock far *MyBlock = *CurBlockPtr;

  int left = (MyBlock->OwnerDoc->BlockSize - MyBlock->LWAp1);

  if (LineToAdd->length <= (int) left)
    {

      /* There's room in the current block for this line, so just    */
      /* move lines down to accomodate this line and copy it in.     */

      MoveBytes ((char far *) *CurAddPtr,
		 ((char far *) *CurAddPtr) + LineToAdd->length,
       (((char far *) MyBlock + MyBlock->LWAp1)) - (char far *) *CurAddPtr);
      MoveBytes ((char far *) LineToAdd, (char far *) *CurAddPtr,
		 LineToAdd->length);

      /* Adjust textblock counters.                                  */
      MyBlock->LWAp1 += LineToAdd->length;
      MyBlock->NumLines++;
      IncPtr ((*CurAddPtr), LineToAdd->length);
      MyBlock->OwnerDoc->TotalLines++;
    }
  else
    {

      /* There isn't enough room in the current textblock.           */
      /* If we are at the end of the current block, just create      */
      /* a new empty one; otherwise, split the current block.        */

      TypBlock far *NewBlockPtr;

      if ((*CurAddPtr)->length == END_OF_BLOCK)
	{
	  /* We're at end of block; create a new empty one.           */
	  /* This will be the current block, so release references    */
	  /* to the now-current block                                 */
	  if (NewBlock (MyBlock, CurBlockPtr))
	    {
	      return (1);
	    }
	  else
	    {
	      GlobalUnlock (MyBlock->hCurBlock);
	      *CurAddPtr = (TypLine far *)
		((char far *) *CurBlockPtr + sizeof (TypBlock));
	      /* Now we can recursively try again to add the line.     */
	      if (AddLine (LineToAdd, CurBlockPtr, CurAddPtr))
		{
		  return (1);
		}
	    }
	}
      else
	{
	  /* We need to split the textblock.                          */
	  /* Find a place to split the block by starting at the       */
	  /* beginning of the block and skipping through the lines    */
	  /* until we pass the number of bytes that marks the         */
	  /* split point.  The previous line is the split point.      */

	  TypLine far *MyLinePtr = (TypLine far *) ((char far *) MyBlock + sizeof (TypBlock));
	  TypLine far *MyLastLine = MyLinePtr;
	  int nOldLines = 0, nBytesMoved, MyAddOffset;

	  while ((char far *) MyLinePtr - (char far *) MyBlock < MyBlock->OwnerDoc->SplitSize &&
		 MyLinePtr->length != END_OF_BLOCK)
	    {
	      nOldLines++;
	      MyLastLine = MyLinePtr;
	      IncPtr (MyLinePtr, MyLinePtr->length);
	    }

	  /* Allocate the new block and copy the last portion of      */
	  /* the current block to the new one.   The range to         */
	  /* copy starts at the above-determined split point and      */
	  /* goes until the LWA+1.                                    */
	  /* Then adjust the new & old textblock fields.              */

	  if (NewBlock (MyBlock, &NewBlockPtr))
	    {
	      return (1);
	    }
	  else
	    {
	      MoveBytes (MyLastLine, (char far *) NewBlockPtr + sizeof (TypBlock),
			 nBytesMoved = (((char far *) MyBlock + MyBlock->LWAp1)) - (char far *) MyLastLine);
	      MyBlock->LWAp1 = (char far *) MyLastLine - ((char far *) MyBlock)
		+ sizeof (TypLine);
	      ((TypLine far *) MyLastLine)->length = END_OF_BLOCK;
	      ((TypLine far *) MyLastLine)->LineID = NextLineID++;
	      NewBlockPtr->NumLines = MyBlock->NumLines - nOldLines;
	      MyBlock->NumLines = nOldLines;
	      NewBlockPtr->LWAp1 = nBytesMoved + sizeof (TypBlock);

	      /* Should the new line go in the old block or the new?   */
	      /* If the add point is beyond the end of the newly-      */
	      /* truncated block, we must move the add point to the    */
	      /* next block and make the new block the current one.    */
	      /* The new position should be the same                   */
	      /* number of line bytes past the beginning of the next   */
	      /* block as it was past the split point when it was in   */
	      /* the old block.                                        */
	      /* Either way, one block (the old or the new one)        */
	      /* must be unlocked.                                     */

	      if (*CurAddPtr >= MyLastLine)
		{
		  /* Add point is in new block.                         */

		  MyAddOffset = (char far *) *CurAddPtr - (char far *) MyLastLine + sizeof (TypBlock);
		  *CurAddPtr = (TypLine far *) ((char far *) NewBlockPtr + MyAddOffset);
		  *CurBlockPtr = NewBlockPtr;
		  GlobalUnlock (MyBlock->hCurBlock);
		}
	      else
		{
		  /* Add point is in current block.                     */
		  GlobalUnlock (NewBlockPtr->hCurBlock);
		}
	      return (AddLine (LineToAdd, CurBlockPtr, CurAddPtr));
	    }
	}
    }
  return (0);
}

/*-- function ReplaceLine -------------------------------------------------
 *
 *  Replace a line in a textblock.  Create a new textblock if necessary.
 *
 *    Entry    LineToAdd   is the line to put into a textblock.
 *             CurBlockPtr points to the block containing the old copy.
 *             CurLinePtr  points to the old copy of the line.
 *
 *    Exit     returns TRUE if successful.
 *             CurBlockPtr points to the block containing the new copy of the line.
 *             CurLinePtr  points to the new copy of the line.
 *                         Usually, CurBlockPtr & CurAddPtr will be the
 *                         same upon exit as upon entry; however, sometimes
 *                         a textblock split is necessary.
 *
 *    Method   There are two cases:
 *             1. There is enough room in this textblock for the
 *                changed line.
 *                This is pretty simple.
 *             2. There is not enough room in this textblock for the
 *                changed line.  This requires a textblock split,
 *                as with AddLine.  There's a lot of common code
 *                here--should probably consolidate it in a single routine
 *                some day.
 */
BOOL
ReplaceLine (LineToAdd, CurBlockPtr, CurLinePtr)
     TypLine *LineToAdd;
     TypBlock far **CurBlockPtr;
     TypLine far **CurLinePtr;
{
  TypBlock far *MyBlockPtr = *CurBlockPtr;
  TypLine far *MyLinePtr = *CurLinePtr;
  int deltasize;
  int numbytes;
  char far *target, far * source;

  deltasize = LineToAdd->length - (MyLinePtr->length);

  if (deltasize <= (int) (MyBlockPtr->OwnerDoc->BlockSize - MyBlockPtr->LWAp1))
    {

      /* There's room in the current block for this line, so just    */
      /* move lines down to accomodate this line and copy it in.     */
      /* Move the data in the textblock up or down, starting with    */
      /* the line after the line being replaced.                     */

      source = (char far *) MyLinePtr + MyLinePtr->length;
      target = source + deltasize;
      numbytes = ((char far *) MyBlockPtr + MyBlockPtr->LWAp1) -
	(char far *) MyLinePtr - MyLinePtr->length;
#if 0
      numbytes = (char far *) MyBlockPtr + MyBlockPtr->LWAp1 - (char far *) MyLinePtr;
      target = (char far *) MyLinePtr + deltasize;
#endif
      MoveBytes (source, target, numbytes);

      MoveBytes ((char far *) LineToAdd, (char far *) MyLinePtr,
		 LineToAdd->length);
      MyBlockPtr->LWAp1 += deltasize;
    }
  else
    {

      /* There isn't enough room in the current textblock.           */
      /* We need to split the textblock.                          */
      /* Find a place to split the block by starting at the       */
      /* beginning of the block and skipping through the lines    */
      /* until we pass the number of bytes that marks the         */
      /* split point.  The previous line is the split point.      */

      TypBlock far *NewBlockPtr;

      TypLine far *MyLinePtr = (TypLine far *) ((char far *) MyBlockPtr + sizeof (TypBlock));
      TypLine far *MyLastLine = MyLinePtr;
      int nOldLines = 0, nBytesMoved, MyAddOffset;

#if 0
      MessageBox (hWndConf, "Need to split textblock", "in ReplaceLine", MB_OK);
#endif

      while (((char far *) MyLinePtr - (char far *) MyBlockPtr) < MyBlockPtr->OwnerDoc->SplitSize &&
	     MyLinePtr->length != END_OF_BLOCK)
	{
	  nOldLines++;
	  MyLastLine = MyLinePtr;
/*         (char far *) MyLinePtr += MyLinePtr->length; */
	  IncPtr (MyLinePtr, MyLinePtr->length);
	}

      /* Allocate the new block and copy the last portion of      */
      /* the current block to the new one.   The range to         */
      /* copy starts at the above-determined split point and      */
      /* goes until the LWA+1.                                    */
      /* Then adjust the new & old textblock fields.              */

      if (NewBlock (MyBlockPtr, &NewBlockPtr))
	{
	  return (1);
	}
      else
	{
	  MoveBytes (MyLastLine, (char far *) NewBlockPtr + sizeof (TypBlock),
		     nBytesMoved = (((char far *) MyBlockPtr + MyBlockPtr->LWAp1)) - (char far *) MyLastLine);
	  MyBlockPtr->LWAp1 = (char far *) MyLastLine - ((char far *) MyBlockPtr)
	    + sizeof (TypLine);
	  ((TypLine far *) MyLastLine)->length = END_OF_BLOCK;
	  ((TypLine far *) MyLastLine)->LineID = NextLineID++;
	  NewBlockPtr->NumLines = MyBlockPtr->NumLines - nOldLines;
	  MyBlockPtr->NumLines = nOldLines;
	  NewBlockPtr->LWAp1 = nBytesMoved + sizeof (TypBlock);

	  /* Should this line go in the old block or the new?   */
	  /* If the add point is beyond the end of the newly-      */
	  /* truncated block, we must move the add point to the    */
	  /* next block and make the new block the current one.    */
	  /* The new position should be the same                   */
	  /* number of line bytes past the beginning of the next   */
	  /* block as it was past the split point when it was in   */
	  /* the old block.                                        */
	  /* Either way, one block (the old or the new one)        */
	  /* must be unlocked.                                     */

	  if (*CurLinePtr >= MyLastLine)
	    {
	      /* Replace point is in new block.                         */

	      MyAddOffset = (char far *) *CurLinePtr - (char far *) MyLastLine + sizeof (TypBlock);
	      *CurLinePtr = (TypLine far *) ((char far *) NewBlockPtr + MyAddOffset);
	      *CurBlockPtr = NewBlockPtr;
	      GlobalUnlock (MyBlockPtr->hCurBlock);
	    }
	  else
	    {
	      /* Add point is in current block.                     */
	      GlobalUnlock (NewBlockPtr->hCurBlock);
	    }
	  return (ReplaceLine (LineToAdd, CurBlockPtr, CurLinePtr));
	}
    }
  return (TRUE);
}

/*-- function DeleteLine ----------------------------------------------
 *
 *  Delete a line from a textblock.
 *
 *    Entry    CurBlockPtr points to the block containing the line.
 *             CurLinePtr  points to the line to delete.
 *
 *    Exit     returns TRUE if successful.
 *             CurBlockPtr points to the block containing the next line,
 *                         if any.
 *             CurLinePtr  points to the next line, if any--else the
 *                         end of the block.
 *                         Usually, CurBlockPtr & CurLinePtr will be the
 *                         same upon exit as upon entry; however, sometimes
 *                         a textblock is emptied and the entire block
 *                         is deleted.
 */
BOOL
DeleteLine (TypBlock far ** CurBlockPtr, TypLine far ** CurLinePtr)
{
  TypBlock far *MyBlockPtr = *CurBlockPtr;
  TypLine far *MyLinePtr = *CurLinePtr;
  int bytes_to_end, bytes_to_copy;
  int cur_length = MyLinePtr->length;

  /* If we are (erroneously) at the end of a block, do nothing */

  if (MyLinePtr->length == END_OF_BLOCK)
    {
      return (FALSE);
    }

  /* Copy the remainder of the block on top of the line to be deleted. */

  bytes_to_end = ((char far *) MyBlockPtr + MyBlockPtr->LWAp1) -
    (char far *) MyLinePtr;
  bytes_to_copy = bytes_to_end - cur_length;
  MoveBytes ((char far *) MyLinePtr + cur_length, (char far *) MyLinePtr,
	     bytes_to_copy);

  /* Update the block counters.  */

  MyBlockPtr->LWAp1 -= cur_length;
  MyBlockPtr->NumLines--;
  MyBlockPtr->OwnerDoc->TotalLines--;

  /* If we are now at the end of the block, we are faced with one of
   * two situations:
   * 1.  We are also at the beginning of the block, and hence
   *     the block is empty.  In this case we must delete the block
   *     unless it is the only block.
   * 2.  Otherwise, we must advance to the next block, if any.
   */

  if (MyLinePtr->length == END_OF_BLOCK)
    {

      /* We are at the end of the block.  */

      if (*((int far *) (MyLinePtr) - 1) == END_OF_BLOCK)
	{

	  /* We have emptied the block.  We must check for whether
           * this is the last block in the document.
           */
	  if (MyBlockPtr->OwnerDoc->TotalLines)
	    {
	      /* The document is not empty.  Delete this empty block.
               */
	      DeleteBlock (CurBlockPtr, CurLinePtr);
	    }
	  else
	    {
	      /* The document is empty.  Don't delete this block.
               * Leave the pointer at the same place.  It is now pointing
               * at the next line.
               */
	    }
	}
      else
	{
	  /* We're at the end of the block, but the block is not empty.
           * Just advance to the next block, if any.
           */
	  NextLine (CurBlockPtr, CurLinePtr);
	}
    }

  return (TRUE);
}


/*-- function NextLine ------------------------------------------------
 *
 *  Advance a pointer to point to the next line in a document.
 *
 *    Entry    BlockPtr    points to the current block.
 *             LinePtr     points to the current line.
 *
 *    Exit     BlockPtr & LinePtr point to the next line, if there
 *              was one.
 *             Returns TRUE iff pointer was moved.
 *
 *    Method   Must advance BlockPtr if LinePtr was at the end
 *             of a block to start with, or arrives at the end of a block
 *             after moving to the end of the current line.
 */
int
NextLine (BlockPtr, LinePtr)
     TypBlock far **BlockPtr;
     TypLine far **LinePtr;
{
  BOOL retcode = 0;

  if ((*LinePtr)->length != END_OF_BLOCK)
    {
/*      (char far *) *LinePtr += (*LinePtr)->length; */
      IncPtr (*LinePtr, (*LinePtr)->length);
    }
  if ((*LinePtr)->length == END_OF_BLOCK)
    {
      if ((*BlockPtr)->hNextBlock)
	{
	  GlobalUnlock ((*BlockPtr)->hCurBlock);
	  *BlockPtr = (TypBlock far *) GlobalLock ((*BlockPtr)->hNextBlock);
	  *LinePtr = (TypLine far *) ((char far *) *BlockPtr + sizeof (TypBlock));
	  retcode = 1;
	}
    }
  else
    {
      retcode = 1;
    }
  return (retcode);
}

/*-- function PrevLine ------------------------------------------------
 *
 *  Back up a pointer to point to the next line in a document.
 *
 *    Entry    BlockPtr    points to the current block.
 *             LinePtr     points to the current line.
 *
 *    Exit     BlockPtr & LinePtr point to the previous line, if there
 *              was one.
 *             Returns TRUE iff pointer was moved.
 *
 *    Method   Rely on the fact that the last field of the previous
 *             line is the length of that line.  Also, the last field
 *             in a textblock header (which is what you get if you
 *             try to look at the previous line in a textblock if you're
 *             at the beginning of a textblock) has the value END_OF_BLOCK.
 */
int
PrevLine (BlockPtr, LinePtr)
     TypBlock far **BlockPtr;
     TypLine far **LinePtr;
{
  if (*((int far *) (*LinePtr) - 1) != END_OF_BLOCK)
    {
/*      (char far *) *LinePtr -= *((int far *)(*LinePtr)-1); */
      IncPtr (*LinePtr, -(*((int far *) (*LinePtr) - 1)));
    }
  else
    {
      if ((*BlockPtr)->hPrevBlock)
	{
	  GlobalUnlock ((*BlockPtr)->hCurBlock);
	  *BlockPtr = (TypBlock far *) GlobalLock ((*BlockPtr)->hPrevBlock);
	  *LinePtr = (TypLine far *)
	    ((char far *) *BlockPtr + (*BlockPtr)->LWAp1 - sizeof (TypLine));
/*         (char far *) *LinePtr -= ( *((int far *)(*LinePtr)-1)); */
	  IncPtr (*LinePtr, -(*((int far *) (*LinePtr) - 1)));
	}
      else
	{
	  return (0);
	}
    }
  return (1);
}

/*--- function TopOfDoc ------------------------------------------------
 *
 *   Set pointers to the first line of a document.
 *
 *    Entry    Doc         points to a document.
 *
 *    Exit     BlockPtr, LinePtr   point to the first
 *                         line in the document.  This line is locked.
 */
void
TopOfDoc (Doc, BlockPtr, LinePtr)
     TypDoc *Doc;
     TypBlock far **BlockPtr;
     TypLine far **LinePtr;
{
  HANDLE hBlock;
  int Offset;
  TypLineID MyLineID;

  hBlock = Doc->hFirstBlock;
  Offset = sizeof (TypBlock);
  MyLineID = 0L;
  LockLine (hBlock, Offset, MyLineID, BlockPtr, LinePtr);
}

/*--- function ExtractTextLine -----------------------------------------------
 *
 *  Extract the text portion of a line to another buffer.
 *
 *    Entry    Doc      points to the document.
 *             LinePtr  points to a line.
 *             BufSize  is the size of the output buffer in bytes.
 *
 *    Exit     Buf      contains the text, terminated by a zero byte.
 *             Returns FALSE iff no text could be extracted
 *               (not a valid line).
 */
BOOL
ExtractTextLine (Doc, LinePtr, Buf, BufSize)
     TypDoc *Doc;
     TypLine far *LinePtr;
     char *Buf;
     int BufSize;
{
  char far *bptr;
  BOOL DidIt = FALSE;

  if (LinePtr->length == END_OF_BLOCK)
    {
    }
  else
    {
      if (Doc->DocType == DOCTYPE_NET)
	{
	  bptr = (char far *) LinePtr + sizeof (TypLine) + sizeof (TypGroup);
	  while (--BufSize > 1 && (*(Buf++) = *(bptr++)));
	  *Buf = '\0';
	  DidIt = TRUE;
	}
      else if (Doc->DocType == DOCTYPE_ARTICLE)
	{
	  bptr = (char far *) LinePtr + sizeof (TypLine) + sizeof (TypText);
	  while (--BufSize > 1 && (*(Buf++) = *(bptr++)));
	  *Buf = '\0';
	  DidIt = TRUE;
	}
    }
  return (DidIt);
}

/*-- function LockLine ---------------------------------------------
 *
 *  Find the specified line, and return a pointer to it.
 *  Lock the line in memory.
 *
 *  Entry:  hBlock      is the handle of a block we think contains
 *                      the desired line.
 *          LineOff     is the offset in bytes from the beginning of
 *                      the block for where we think the line is.
 *          FindLineID  is the LineID of the desired line.
 *                      If it is 0, we don't check line ID's (don't care).
 *
 *  Exit:   returns     TRUE iff the line was found.
 *          BlockPtr    points to the beginning of the block
 *                      in which the line was found.
 *          LinePtr     points to the line.
 */
BOOL
LockLine (hBlock, LineOff, FindLineID, BlockPtr, LinePtr)
     HANDLE hBlock;
     unsigned int LineOff;
     TypLineID FindLineID;
     TypBlock far **BlockPtr;
     TypLine far **LinePtr;
{
  *BlockPtr = (TypBlock far *) GlobalLock (hBlock);
  *LinePtr = (TypLine far *) ((char far *) *BlockPtr + LineOff);
  if (FindLineID && (*LinePtr)->LineID != FindLineID)
    {
      /* The location specified by hBlock and LineOff does not
       * contain the right line.  So, unlock that block and start
       * scanning the document from the top, looking for the line.
       */
      TypBlock far *MyBlockPtr;
      TypLine far *MyLinePtr;
      HANDLE hMyBlock;
      int MyOffset;

#if 0
      MessageBox (hWndConf, "LineID doesn't match", "in LockLine", MB_OK);
#endif

      hMyBlock = (*BlockPtr)->OwnerDoc->hFirstBlock;
      MyOffset = sizeof (TypBlock);
      GlobalUnlock (hBlock);

      MyBlockPtr = (TypBlock far *) GlobalLock (hMyBlock);
      MyLinePtr = (TypLine far *) ((char far *) MyBlockPtr + sizeof (TypBlock));

      while (MyLinePtr->LineID != FindLineID && NextLine (&MyBlockPtr, &MyLinePtr));

      if (MyLinePtr->LineID == FindLineID)
	{
	  *BlockPtr = MyBlockPtr;
	  *LinePtr = MyLinePtr;
	}
      else
	{
	  MessageBox (hWndConf, "Can't find line", "in LockLine", MB_ICONHAND | MB_OK);
	  return (FALSE);
	}
    }
  return (TRUE);
}

/*-- function UnlockLine ---------------------------------------------
 *
 *  Given a block pointer and a line pointer, unlock the block
 *  in memory and return a line ID, a block handle and an offset within the
 *  block to the line.
 *
 *    Entry    BlockPtr    points to a textblock
 *             LinePtr     points to a line in that textblock
 *
 *    Exit     TheLineID   is the LineID of the pointed-to line.
 *             hBlock      is the handle to the textblock.
 *             LineOff     is the offset (in bytes from the beginning
 *                         of the block) of the line.
 */
void
UnlockLine (BlockPtr, LinePtr, hBlock, LineOff, TheLineID)
     TypBlock far *BlockPtr;
     TypLine far *LinePtr;
     HANDLE *hBlock;
     unsigned int *LineOff;
     TypLineID *TheLineID;
{
  PtrToOffset (BlockPtr, LinePtr, hBlock, LineOff, TheLineID);
  GlobalUnlock (*hBlock);
}

/*-- function PtrToOffset ---------------------------------------------
 *
 *  Given a block pointer and a line pointer,
 *  return a line ID, a block handle and an offset within the
 *  block to the line.
 *
 *    Entry    BlockPtr    points to a textblock
 *             LinePtr     points to a line in that textblock
 *
 *    Exit     TheLineID   is the LineID of the pointed-to line.
 *             hBlock      is the handle to the textblock.
 *             LineOff     is the offset (in bytes from the beginning
 *                         of the block) of the line.
 */
void
PtrToOffset (BlockPtr, LinePtr, hBlock, LineOff, TheLineID)
     TypBlock far *BlockPtr;
     TypLine far *LinePtr;
     HANDLE *hBlock;
     unsigned int *LineOff;
     TypLineID *TheLineID;
{
  *TheLineID = LinePtr->LineID;
  *LineOff = (char far *) LinePtr - (char far *) BlockPtr;
  *hBlock = BlockPtr->hCurBlock;
}

/*-- function WhatLine ------------------------------------------------
 *
 *  Determine the ordinal number of a given line in the document.
 *
 *    Entry    BlockPtr    points to the block containing a line.
 *             LinePtr     points to the line.
 *
 *    Exit     Returns 0 = first line, 1 = second, and so on.
 *
 *  Strategy is to start at the beginning of the document and
 *  scan though the lines, counting lines until we reach the
 *  current line.  In more detail:
 *
 *  Save the current position.
 *  Go to the first block of the document.
 *  Number of lines = 0
 *  While we are not yet at the original block,
 *     Add in the number of lines in this block, just by looking at header.
 *  Now that we have reached the original block, start at the
 *     beginning of the block and scan forward line-by-line until
 *     we reach the original line.
 */
unsigned int
WhatLine (BlockPtr, LinePtr)
     TypBlock far *BlockPtr;
     TypLine far *LinePtr;
{
  unsigned int nLines = 0;
  TypBlock far *MyBlock;
  TypLine far *MyLine;
  TypDoc *MyDoc;
  HANDLE hOrgBlock, hMyBlock, hNewBlock;
  unsigned int OrgOffset, MyOffset;
  TypLineID OrgLineID, MyLineID = 0L;

  MyDoc = BlockPtr->OwnerDoc;
  UnlockLine (BlockPtr, LinePtr, &hOrgBlock, &OrgOffset, &OrgLineID);

  hMyBlock = MyDoc->hFirstBlock;
  while (hMyBlock != hOrgBlock)
    {
      MyBlock = (TypBlock far *) GlobalLock (hMyBlock);
      nLines += MyBlock->NumLines;
      hNewBlock = MyBlock->hNextBlock;
      if (!hNewBlock)
	{
	  MessageBox (MyDoc->hDocWnd, "Hit end of document", "Error in WhatLine",
		      MB_OK | MB_ICONHAND);
	}
      GlobalUnlock (hMyBlock);
      hMyBlock = hNewBlock;
    }

  /* The technique of scanning the NumLines field starts the        */
  /* counter at 1 rather than 0, so if we got any lines above,      */
  /* adjust the count by decrementing it.                           */

/* if(nLines) nLines--; */

  /* We have reached the original block.                            */
  /* Start scanning at the first line.                              */

  MyOffset = sizeof (TypBlock);
  LockLine (hMyBlock, MyOffset, MyLineID, &MyBlock, &MyLine);
  while (MyOffset != OrgOffset)
    {
      nLines++;
      NextLine (&MyBlock, &MyLine);
      MyOffset = (char far *) MyLine - (char far *) MyBlock;
    }

  return (nLines);
}

/*--- function NumBlocksInDoc --------------------------------------------
 *
 *  Find the number of blocks in a document.
 *
 * Entry Doc points to a document.
 *
 * Exit  returns the number of textblocks in the document.
 */
int
NumBlocksInDoc (Doc)
     TypDoc *Doc;
{
  TypBlock far *MyBlock;
  HANDLE hMyBlock, hNewBlock;
  int nBlocks = 0;

  if (!Doc)
    return (0);
  hMyBlock = Doc->hFirstBlock;
  do
    {
      nBlocks++;
      MyBlock = (TypBlock far *) GlobalLock (hMyBlock);
      hNewBlock = MyBlock->hNextBlock;
      GlobalUnlock (hMyBlock);
      hMyBlock = hNewBlock;
    }
  while (hNewBlock);

  return (nBlocks);
}

/*--- function FindLineOrd ---------------------------------------------
 *
 *  Find the Nth line in a document.
 *
 *    Entry    Doc      points to a document.
 *             LineOrd  is the ordinal of the line to find.
 *                      0 = first line in document.
 *
 *    Exit     BlockPtr points to the block containing the line.
 *             LinePtr  points to the line.
 *             return value is TRUE iff we found the line.
 */
BOOL
FindLineOrd (Doc, LineOrd, BlockPtr, LinePtr)
     TypDoc *Doc;
     unsigned int LineOrd;
     TypBlock far **BlockPtr;
     TypLine far **LinePtr;
{
  unsigned int LinesSoFar = 0;
  TypBlock far *MyBlockPtr;
  TypLine far *MyLinePtr;
  HANDLE hBlock, hNextBlock;
  int retcode = 0;

  hBlock = Doc->hFirstBlock;
  do
    {
      MyBlockPtr = (TypBlock far *) GlobalLock (hBlock);
      if (LinesSoFar + MyBlockPtr->NumLines > LineOrd)
	break;
      LinesSoFar += MyBlockPtr->NumLines;
      hNextBlock = MyBlockPtr->hNextBlock;
      GlobalUnlock (hBlock);
      hBlock = hNextBlock;
    }
  while (hBlock);

  if (hBlock)
    {
      MyLinePtr = (TypLine far *) ((char far *) MyBlockPtr + sizeof (TypBlock));
      while (LinesSoFar < LineOrd)
	{
	  LinesSoFar++;
	  if (!NextLine (&MyBlockPtr, &MyLinePtr))
	    break;
	}
      retcode = TRUE;
      *BlockPtr = MyBlockPtr;
      *LinePtr = MyLinePtr;
    }
  return (retcode);
}

/*-- function MoveBytes -----------------------------------------------
 *
 *   Move a region of bytes in memory from one place to another.
 *   Handle overlapping regions without destroying the source.
 *
 *    Entry    Source   points to the FWA of the source.
 *             Target   points to the FWA of the target.
 *             NumBytes is the number of bytes to copy (>= 0).
 */
void
MoveBytes (FSource, FTarget, NumBytes)
     void far *FSource, far * FTarget;
     int NumBytes;
{
  char far *Source = FSource;
  char far *Target = FTarget;

  if (Source < Target)
    {
      Source += NumBytes - 1;
      Target += NumBytes - 1;
      while (NumBytes--)
	*(Target--) = *(Source--);
    }
  else
    {
      while (NumBytes--)
	*(Target++) = *(Source++);
    }
}

/*--- function InitDoc ---------------------------------------------------
 *
 *  Initialize the fields of a document.
 */
int
InitDoc (Doc, hWnd, Parent, DType)
     TypDoc *Doc;
     HWND hWnd;
     TypDoc *Parent;
     int DType;
{
  TypBlock far *BlockPtr;
  HANDLE hBlock;

  Doc->hLastBlock = 0;
  Doc->TotalLines = 0;
  Doc->ActiveLines = 0;
  Doc->BlockSize = BLOCK_SIZE;
  Doc->SplitSize = (BLOCK_SIZE * 2) / 3;
  Doc->hDocWnd = hWnd;
  Doc->hLastSeenBlock = 0;
  Doc->TopLineOrd = 0;
  Doc->ParentDoc = Parent;
  Doc->ParentLineID = 0L;
  Doc->SearchStr[0] = '\0';
  Doc->FindLineID = 0L;
  Doc->TopScLineID = 0L;
  Doc->InUse = TRUE;
  Doc->DocType = DType;
  Doc->hFindBlock = 0;

  switch (DType)
    {
    case DOCTYPE_NET:
      Doc->OffsetToText = sizeof (TypLine) + sizeof (TypGroup);
      break;
    case DOCTYPE_GROUP:
      Doc->OffsetToText = sizeof (TypLine) + sizeof (TypArticle);
      break;
    case DOCTYPE_ARTICLE:
      Doc->OffsetToText = sizeof (TypLine) + sizeof (TypText);
      break;
    }

  hBlock = GlobalAlloc (GMEM_MOVEABLE, (long) BLOCK_SIZE);
  if (hBlock)
    {
      BlockPtr = (TypBlock far *) GlobalLock (hBlock);
      SetupEmptyBlock (BlockPtr, hBlock, 0, 0, Doc);

      Doc->hFirstBlock = hBlock;
      Doc->hLastBlock = hBlock;
      Doc->hCurAddBlock = hBlock;
      Doc->AddOffset = sizeof (TypBlock);
      Doc->AddLineID = 0L;
      Doc->hCurTopScBlock = hBlock;
      Doc->TopScOffset = sizeof (TypBlock);
      Doc->TopScLineID = 0L;
      Doc->LastSeenLineID = 0L;

      GlobalUnlock (hBlock);
    }
  else
    {
      MessageBox (hWnd, "Could not allocate textblock", "Out of Memory Error", MB_OK | MB_ICONHAND);
    }

  return (0);
}

/*-- function FreeDoc ----------------------------------------------
 *
 *  Free up all the text blocks associated with a document.
 *
 *    Entry    Doc   points to the document in question.
 */
void
FreeDoc (Doc)
     TypDoc *Doc;
{
  TypBlock far *BlockPtr;
  HANDLE hBlock, hNextBlock;

  /* Start at the first block of the document, and travel           */
  /* down the linked list of blocks, freeing them.                  */

  hBlock = Doc->hFirstBlock;
  while (hBlock)
    {
      BlockPtr = (TypBlock far *) GlobalLock (hBlock);
      hNextBlock = BlockPtr->hNextBlock;
      GlobalUnlock (hBlock);
      GlobalFree (hBlock);
      hBlock = hNextBlock;
    }
}

/*--- function ForAllLines ---------------------------------------------
 *
 *  Perform an operation for all lines in a document.  The operation
 *  to be performed is specified by a C function argument.
 *
 *    Entry Doc            is the document.
 *          lpfnFunc       is a pointer to the function to call for
 *                         each line.
 *          lFlag          is a flag that's passed to the function.
 *
 */
void
ForAllLines (TypDoc *Doc, 
       void lpfnFunc(TypDoc *Doc, TypBlock far ** BlockPtr, TypLine far ** LinePtr, int wFlag, int wValue),
       int wFlag, int wValue)
{
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  TypLineID old_lineID;
  BOOL looping = TRUE;


  TopOfDoc (Doc, &BlockPtr, &LinePtr);
  if (LinePtr->length != END_OF_BLOCK)
    {
      do
	{
	  old_lineID = LinePtr->LineID;
	  lpfnFunc (Doc, &BlockPtr, &LinePtr, wFlag, wValue);
	  if (old_lineID == LinePtr->LineID &&
	      LinePtr->length != END_OF_BLOCK)
	    {
	      looping = NextLine (&BlockPtr, &LinePtr);
	    }
	  else if (LinePtr->length == END_OF_BLOCK)
	    {
	      looping = FALSE;
	    }
	}
      while (looping);
    }
/*   UnlockLine (BlockPtr, LinePtr,
    &(FindDoc->hFindBlock), &(FindDoc->FindOffset), &(FindDoc->FindLineID)); */

}


/*--- function FindString ----------------------------------------------
 *
 *   Locate a search string in a document.
 *
 *   Entry  StartAtTop        is TRUE iff we should start the search at
 *                            the top of the document.
 *          FindDoc           points to the document in which we are searching.
 *          ->hFindBlock      is the block to start at, if StartAtTop
 *                            is FALSE.
 *          ->FindOffset      is the offset within the block of the
 *                            line to start at, if StartAtTop is FALSE.
 *          ->SearchStr       has the string to search for.
 *
 *   Exit   returns -1 if the string was not found,
 *            else the offset of the string from the beginning of the line.
 *          FindDoc ...
 *          ->hFindBlock      has the block handle of the line which
 *                            was found (if any)
 *          ->FindOffset      has the offset of the found line (if any)
 *
 */
int
FindString (StartAtTop)
     BOOL StartAtTop;
{
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  HANDLE hBlock;
  unsigned int Offset;
  unsigned int TextOffset;
  BOOL found = -1;
  int TargLen = 0;
  int SourceLen;
  char *Target;
  char sourceline[MAXINTERNALLINE];
  char targline[MAXFINDSTRING];
  char *sourceptr, far * orglineptr;
  char *targptr;
  TypLineID MyLineID;
  register char ch;

  if (StartAtTop)
    {
      hBlock = FindDoc->hFirstBlock;
      Offset = sizeof (TypBlock);
      MyLineID = 0L;
    }
  else
    {
      hBlock = FindDoc->hFindBlock;
      Offset = FindDoc->FindOffset;
      MyLineID = FindDoc->FindLineID;
    }
  LockLine (hBlock, Offset, MyLineID, &BlockPtr, &LinePtr);
  /* If doing a Find Next, skip forward one line before starting    */
  /* the search.                                                    */
  if (!StartAtTop)
    {
      NextLine (&BlockPtr, &LinePtr);
    }

  TextOffset = FindDoc->OffsetToText;

  Target = FindDoc->SearchStr;
  for (targptr = targline; ch = *Target, *(targptr++) = tolower (ch);
       TargLen++)
    Target++;

  if (LinePtr->length != END_OF_BLOCK)
    {
      do
	{
	  orglineptr = (char far *) LinePtr + TextOffset;
	  sourceptr = sourceline;
	  for (SourceLen = 0; ch = *(orglineptr),
	       *(sourceptr++) = tolower (ch); SourceLen++)
	    orglineptr++;
	  found = SearchLine (sourceline, SourceLen, targline, TargLen);
	}
      while (found == -1 && NextLine (&BlockPtr, &LinePtr));
    }
  UnlockLine (BlockPtr, LinePtr,
    &(FindDoc->hFindBlock), &(FindDoc->FindOffset), &(FindDoc->FindLineID));
  return (found);
}

/*--- function SearchLine -----------------------------------------------
 *
 *  Search a line for a target string.
 *
 *  Entry   Line     is a zero-terminated string to search.
 *          LineLen  is strlen(Line).  Redundant, but passed for
 *                   efficiency.
 *          Target   is the Target string to search for, zero-terminated.
 *          TargLen  is strlen(Target), passed for efficiency.
 *
 *  Exit    returns  -1 if not found, else the character position
 *                   in which the string was found (0=first).
 */
int
SearchLine (Line, LineLen, Target, TargLen)
     char *Line;
     int LineLen;
     char *Target;
     int TargLen;
{
  char *stopptr = Line + LineLen - TargLen + 1;
  char *lineptr;
  char *searchptr;
  char *targptr;

  if (LineLen <= 0 || TargLen <= 0 || TargLen > LineLen)
    return (-1);

  for (lineptr = Line; lineptr != stopptr; lineptr++)
    {
      searchptr = lineptr;
      for (targptr = Target; *targptr && *(targptr) == *(searchptr);)
	{
	  targptr++;
	  searchptr++;
	}
      if (!(*targptr))
	{
	  return (lineptr - Line);
	}
    }
  return (-1);
}

/*--- function DoFind ---------------------------------------------------
 *
 *  Controlling routine for searching for text.
 *  Takes care of displaying window properly when search is done.
 *
 *    Entry    StartAtTop  is TRUE iff we should start at the top
 *                         of the document.
 *             FindDoc     points to the document being searched.
 *                         All the info we need is in fields in this doc.
 */
BOOL
DoFind (StartAtTop)
     BOOL StartAtTop;
{
  int CharPos;
  int iline;
  int X, Y;
  HDC hDC;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  HANDLE hBlock;
  unsigned int Offset;
  char far *textptr;
  POINT point;
  int mylen;
  int found = FALSE;
  BOOL refresh = FALSE;
  int goline;
  unsigned int LineOrd;
  unsigned int LastAllowedLineOrd;
  TypLineID MyLineID;

  CharPos = FindString (StartAtTop);
  if (CharPos >= 0)
    {
      iline = LineOnScreen (FindDoc, FindDoc->hFindBlock, FindDoc->FindOffset, FindDoc->FindLineID);
      /* If this line wasn't on the screen, we are going to have
       * to adjust the top of the screen.
       * Make the found line the top of the screen, then back up a
       * little to give the user a context in which to view the line.
       */
      if (iline == -1)
	{
	  FindDoc->hCurTopScBlock = FindDoc->hFindBlock;
	  FindDoc->TopScOffset = FindDoc->FindOffset;
	  FindDoc->TopScLineID = FindDoc->FindLineID;
	  refresh = TRUE;
	  LockLine (FindDoc->hCurTopScBlock, FindDoc->TopScOffset, FindDoc->TopScLineID,
		    &BlockPtr, &LinePtr);
	  for (goline = FindDoc->ScYLines / 4; goline; goline--)
	    {
	      PrevLine (&BlockPtr, &LinePtr);
	    }
	  /* Have we gone past the top of the last screen?
           * If so, move the top line back to the top of the last screen.
           */
	  LineOrd = WhatLine (BlockPtr, LinePtr);
	  LastAllowedLineOrd = FindDoc->TotalLines - FindDoc->ScYLines;
	  if (LineOrd > LastAllowedLineOrd)
	    {
	      GlobalUnlock (BlockPtr->hCurBlock);
	      FindLineOrd (FindDoc, LastAllowedLineOrd, &BlockPtr, &LinePtr);
	      LineOrd = LastAllowedLineOrd;
	    }
	  UnlockLine (BlockPtr, LinePtr, &(FindDoc->hCurTopScBlock),
		      &(FindDoc->TopScOffset), &(FindDoc->TopScLineID));
	  FindDoc->TopLineOrd = LineOrd;
	  iline = LineOnScreen (FindDoc, FindDoc->hFindBlock, FindDoc->FindOffset, FindDoc->FindLineID);
	}
#if 0

      LockLine (FindDoc->hFindBlock, FindDoc->FindOffset, FindDoc->FindLineID,
		&BlockPtr, &LinePtr);
      textptr = (char far *) LinePtr + FindDoc->OffsetToText;
      hDC = GetDC (FindDoc->hDocWnd);
      SelectObject (hDC, hFont);
      point.x = SideSpace + LOWORD (GetTextExtent (hDC, textptr, CharPos));
      ReleaseDC (FindDoc->hDocWnd, hDC);
      UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);

      point.y = TopSpace + iline * LineHeight + 4 * LineHeight / 5;
      ClientToScreen (FindDoc->hDocWnd, &point);
      SetCursorPos (point.x, point.y);
#endif

      InvalidateRect (FindDoc->hDocWnd, NULL, FALSE);
      found = TRUE;
    }
  return (found);
}
