/*---  wvgroup.c ------------------------------------------- */
/*  This file contains the window procedure for the Group Viewing window
 *  for WinVN.
 */

#include <windows.h>
#ifndef MAC
#include "winundoc.h"
#endif
#include "WVglob.h"
#include "WinVn.h"
#ifdef MAC
#include "MRRM1.h"
#include <DialogMgr.h>
#endif

long NewCursorToTextLine (int X, int Y, TypDoc * DocPtr);
long search_headers (TypDoc * HeaderDoc, TypHeader *headers, long artindex, long num_headers);
LPSTR string_compare_insensitive (LPSTR a, LPSTR b);

/*--- FUNCTION: WinVnViewWndProc --------------------------------------------
 *
 *    Window procedure for a Group window, which contains the subjects
 *    of the various articles in a newsgroup.
 *    Note that there may be several different Group windows active;
 *    this routine gets called any time anything happens to any of them.
 */

long FAR PASCAL 
WinVnViewWndProc (hWnd, message, wParam, lParam)
     HWND hWnd;
     UINT message;
     WPARAM wParam;
     LPARAM lParam;
{
  FARPROC lpProcAbout;
  HMENU hMenu;

  PAINTSTRUCT ps;		/* paint structure          */

  HDC hDC;			/* handle to display context */
  RECT clientRect;		/* selection rectangle      */
  HDC hDCView;
  TypDoc *ThisDoc;
  int ih, j;
  int iart;
  long artindex;
  int found;
  int imemo;
  int CtrlState;
  TypBlock far *BlockPtr, far * ArtBlockPtr;
  TypLine far *LinePtr, far * ArtLinePtr;
  HANDLE hBlock;
  unsigned int Offset;
  char mybuf[MAXINTERNALLINE];
  TypDoc *MyDoc;
  TypLineID MyLineID;
  POINT ptCursor;

  int X, Y, nWidth, nHeight;
  int mylen;
  int OldSel = FALSE;

  /* We know what *window* is being acted on, but we must find
   * out which *document* is being acted on.  There's a one-to-one
   * relationship between the two, and we find out which document
   * corresponds to this window by scanning the GroupDocs array.
   */

  for (ih = 0, found = FALSE; !found && ih < MAXGROUPWNDS; ih++)
    {
      if (GroupDocs[ih].hDocWnd == hWnd)
	{
	  found = TRUE;
	  ThisDoc = &(GroupDocs[ih]);
	}
    }

  if (!found)
    {
      ThisDoc = CommDoc;
    }

  switch (message)
    {
    case WM_ACTIVATE:
      if (wParam)
	{
	  ActiveGroupDoc = ThisDoc;
#if 0
	  SetMenuBar (groupMenuBar);
	  DrawMenuBar ();
#endif
	}
      /* fall through */
    case WM_SYSCOMMAND:
      return (DefWindowProc (hWnd, message, wParam, lParam));

    case WM_SIZE:
      GetClientRect (hWnd, &clientRect);
      ThisDoc->ScXWidth = clientRect.right;
      ThisDoc->ScYHeight = clientRect.bottom;
      ThisDoc->ScYLines = (clientRect.bottom - clientRect.top - TopSpace) / LineHeight;
      ThisDoc->ScXChars = (clientRect.right - clientRect.left - SideSpace) / CharWidth;
      break;

    case WM_DESTROY:
      /* Unlink all the article windows that belong to this group */

      UpdateSeenArts (ThisDoc);
      UnlinkArtsInGroup (ThisDoc);
      ThisDoc->InUse = FALSE;
      if (ThisDoc == CommDoc)
	{
	  CommBusy = FALSE;
	  CommDoc = (TypDoc *) NULL;
	}
      /* Clear the pointer in the line for this group in the   */
      /* NetDoc document.  This pointer currently points       */
      /* to the current document, which we are wiping out      */
      /* with the destruction of this window.                  */

      LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset, ThisDoc->ParentLineID, &BlockPtr, &LinePtr);
      {
	TypGroup far *group =
         (TypGroup far *) (((char far *) LinePtr) + sizeof (TypLine));

	group->SubjDoc = (TypDoc *) NULL;
        /* Free the header table */
	if (group->header_handle)
          GlobalFree (group->header_handle);
	group->header_handle = (HANDLE) NULL;
	group->total_headers = 0;
      }

      UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
      /* Clear document                                        */
      FreeDoc (ThisDoc);

      /* If there's another group window, make it the active   */
      /* group window so we don't create a new one if the      */
      /* New Group flag is FALSE.                              */

      for (j = MAXGROUPWNDS - 1; j >= 0; j--)
	{
	  if (GroupDocs[j].InUse)
	    {
	      ActiveGroupDoc = &(GroupDocs[j]);
	      break;
	    }
	}
      break;

    case WM_KEYDOWN:
      /* See if this key should be mapped to a scrolling event
       * for which we have programmed the mouse.  If so,
       * construct the appropriate mouse call and call the mouse code.
       */
#ifndef MAC
      if (wParam == VK_F6)
	{
	  NextWindow (ThisDoc);
	}
      else
	{
	  CtrlState = GetKeyState (VK_CONTROL) < 0;
	  for (j = 0; j < NUMKEYS; j++)
	    {
	      if (wParam == key2scroll[j].wVirtKey &&
		  CtrlState == key2scroll[j].CtlState)
		{
		  SendMessage (hWnd, key2scroll[j].iMessage,
			       key2scroll[j].wRequest, 0L);
		  break;
		}
	    }
	}
#endif
      break;


    case WM_CHAR:
      /* Carriage Return means the same as double-clicking
       * on where the cursor is currently pointing.
       */
      if (wParam == '\r')
	{
	  GetCursorPos (&ptCursor);
	  ScreenToClient (hWnd, &ptCursor);
#ifdef MAC
	  X = ptCursor.h;
	  Y = ptCursor.v;
#else
	  X = ptCursor.x;
	  Y = ptCursor.y;
#endif
	  goto getarticle;
	}
      else
	{
	}
      break;

    case WM_LBUTTONDOWN:
      /*  Clicking the left button on an article name toggles the
       *  selected/not selected status of that article.
       *  Currently selected articles are displayed in reverse video.
       */
      break;

    case WM_LBUTTONDBLCLK:
      /*  Double-clicking on an article subject creates an "Article"
       *  window, whose purpose is to display the article.
       */
      X = LOWORD (lParam);
      Y = HIWORD (lParam);
    getarticle:;

      artindex = NewCursorToTextLine (X, Y, ThisDoc);
	if (artindex > -1 )
	{
	  ViewArticle (ThisDoc, artindex, FALSE);
	}

      break;

    case WM_VSCROLL:
      NewScrollIt (ThisDoc, wParam, lParam);
      break;

    case WM_PAINT:
      {
	HANDLE hBlock;
	int MyLen, width;
	unsigned int Offset;
	int VertLines, HorzChars;
	int EndofDoc = FALSE;
	int RangeHigh, CurPos;
	int RestX, indicatorwidth, Xtext;
	char far *textptr;
	char indicator;
	char *indcptr;
	TypArticle far *MyArt;
	unsigned int artindex;
      	long int artnum;
	TypBlock far *BlockPtr;
	TypLine far *LinePtr;
	TypBlock far *NetBlockPtr;
	TypLine far *NetLinePtr;
	TypGroup far *group;
      	TypHeader *headers;
	HANDLE header_handle;
      	char scratch_line [MAXINTERNALLINE];
	long int OldHighestSeen;
	HANDLE hBlackBrush;
	DWORD MyColors[4], MyBack[4];
#ifdef MAC
	RECT myRect;
	POINT myPoint;
#endif
	DWORD Rop;
	int MyColorMask = 0;
	int PrevColorMask = MyColorMask;
	SIZE string_size;

	/* MyColors and MyBack are arrays of colors used to display text
         * foreground and background.
         * The ColorMask variables are indices into these arrays.
         * We set and clear bits in these indices depending upon
         * whether the article has been selected or seen.
         */

#define SEEN_MASK 1
#define SELECT_MASK 2
	hDC = BeginPaint (hWnd, &ps);

	GetClientRect (hWnd, &clientRect);
	SelectObject (hDC, hFont);

	VertLines = ( (ThisDoc->ScYLines > (ThisDoc->TotalLines - ThisDoc->TopLineOrd))
                     ? (ThisDoc->TotalLines - ThisDoc->TopLineOrd )
                     : ThisDoc->ScYLines);

	HorzChars = ThisDoc->ScXChars;

	MyColors[0] = GetTextColor (hDC);	/* black */
	MyColors[1] = GroupSeenColor;
	MyColors[2] = GetBkColor (hDC);	/* white */
	MyColors[3] = MyColors[1];	/* blue  */

	MyBack[0] = MyColors[2];/* white */
	MyBack[1] = MyColors[2];
	MyBack[2] = MyColors[0];
	MyBack[3] = MyColors[0];

/*	LockLine (ThisDoc->hCurTopScBlock, ThisDoc->TopScOffset, ThisDoc->TopScLineID,
		  &BlockPtr, &LinePtr);
*/
	/* Update the scroll bar thumb position.                 */


	CurPos = ThisDoc->TopLineOrd;
	if (CurPos < 0)
	  CurPos = 0;
	RangeHigh = ThisDoc->TotalLines - VertLines;
	if (RangeHigh < 0)
	  RangeHigh = 0;
	SetScrollRange (hWnd, SB_VERT, 0, RangeHigh, FALSE);
	SetScrollPos (hWnd, SB_VERT, CurPos, TRUE);

	GetTextExtentPoint (hDC, "s 99999 ", 8, &string_size);
      	indicatorwidth = string_size.cx;

	LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset, ThisDoc->ParentLineID,
		  &NetBlockPtr, &NetLinePtr);
	group = (TypGroup far *) ((char far *) NetLinePtr + sizeof (TypLine));

        header_handle = group->header_handle;

      	if (header_handle)
          headers = (TypHeader *) ((char *) GlobalLock (header_handle) + sizeof(char *));

	OldHighestSeen = group->HighestPrevSeen;
	UnlockLine (NetBlockPtr, NetLinePtr, &hBlock, &Offset, &MyLineID);

#ifdef MAC
	myRect.right = ThisDoc->DocClipRect.right;
	myRect.top = 0;
	myRect.bottom = LineHeight;
#endif

	/* Now paint this stuff on the screen.               */

	X = SideSpace;
	Xtext = X + indicatorwidth;
	Y = StartPen;

	artindex = ThisDoc->TopLineOrd;

      	if (ThisDoc->TotalLines)
      	do
	    {
	      artnum = headers[artindex].number;

	    	indicator = ' ';
               	if(headers[artindex].Seen) {
               		indicator = 's';
               	} else if(OldHighestSeen) {
               		if(headers[artindex].number > OldHighestSeen) {
               			indicator = 'n';
               		}
               	}

               if(ThisDoc->FindOffset == artindex)
                 indicator = '>';

	    	sprintf (scratch_line, "%c %u %6.6Fs %20.20Fs %4Fd %Fs ",
	    			indicator,
			    	headers[artindex].number,
			    	headers[artindex].date,
			    	headers[artindex].from,
				headers[artindex].lines,
			    	headers[artindex].subject);

	      MyLen = lstrlen (scratch_line);

	      /* Figure out the color of this line.                 */

	      if (headers[artindex].Seen)
		{
		  MyColorMask |= SEEN_MASK;
		}
	      else
		{
		  MyColorMask &= (0xff - SEEN_MASK);
		}
	      if (headers[artindex].Selected)
		{
		  MyColorMask |= SELECT_MASK;
		  Rop = BLACKNESS;
		}
	      else
		{
		  MyColorMask &= 0xff - SELECT_MASK;
		  Rop = WHITENESS;
		}
	      if (MyColorMask != PrevColorMask)
		{
		  SetTextColor (hDC, MyColors[MyColorMask]);
		  SetBkColor (hDC, MyBack[MyColorMask]);
		  PrevColorMask = MyColorMask;
		}

	      /* Now write out the line.                            */

	      GetTextExtentPoint (hDC, scratch_line, MyLen, &string_size);
	      width = string_size.cx;


	      TextOut (hDC, X, Y, scratch_line, MyLen);
#ifdef MAC
	      GetPen (&myPoint);
	      myRect.left = myPoint.h;
	      FillRect (&myRect, white);

	      myRect.top += LineHeight;
	      myRect.bottom += LineHeight;
#else
	      RestX = X + width;
	      PatBlt (hDC, RestX, Y, clientRect.right - RestX, LineHeight, Rop);
#endif
#if 0
	      if (MyLen < HorzChars)
		{
		  RestX = X + width;
		  /*    TextOut(hDC,RestX,Y,Blanks,MAXINTERNALLINE); */
		  PatBlt (hDC, RestX, Y, clientRect.right - RestX, LineHeight, Rop);
		}
#endif
	      Y += LineHeight;

	    artindex++;

	    }
	  while (--VertLines > 0 );

      	if (header_handle)
      	  GlobalUnlock (header_handle);

	/* We've reached the end of the data to be displayed     */
	/* on this window.  If there's more screen real estate   */
	/* left, just blank it out.                              */

	SetTextColor (hDC, MyColors[0]);
	SetBkColor (hDC, MyBack[0]);
	PatBlt (hDC, 0, Y, clientRect.right, clientRect.bottom - Y, PATCOPY);

	EndPaint (hWnd, &ps);
	break;
      }

    case WM_COMMAND:
      switch (wParam)
	{
	case IDV_EXIT:
	  DestroyWindow (hWnd);
	  break;

	case IDV_NEXT:
	  break;

	case IDM_FIND:
	case IDM_FIND_NEXT_SAME:

          FindDoc = ThisDoc;

	  if (wParam == IDM_FIND)
            DialogBox (hInst, "WinVnFind", hWnd, lpfnWinVnFindDlg);
          if(strcmp(FindDoc->SearchStr,"")) {
          TypGroup far * group;
          TypHeader  *headers;
          HANDLE header_handle;
          int starting_at;

          LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset, ThisDoc->ParentLineID,
		    &BlockPtr, &LinePtr);
          group = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));

          header_handle = group->header_handle;
          headers = (TypHeader  *) ((char *) GlobalLock (header_handle) + sizeof(char  *));

          UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);

          /* 'Find Next' will start one line after */
          starting_at = ThisDoc->TopLineOrd + ((wParam == IDM_FIND) ? 0 : 1);

          /* back up one if we're at the end */
          if (starting_at >= group->total_headers)
            starting_at--;

          found = search_headers (ThisDoc, headers, starting_at, group->total_headers);
 	      if (found == -1)
		{
		  strcpy (mybuf, "\"");
		  strcat (mybuf, ThisDoc->SearchStr);
		  strcat (mybuf, "\" not found.");
		  MessageBox (hWnd, mybuf, "Not found", MB_OK);
		}
               else {
                 ThisDoc->TopLineOrd = found;
                 ThisDoc->FindOffset = found;
                 InvalidateRect(ThisDoc->hDocWnd,NULL,FALSE);
               }

            GlobalUnlock (header_handle);
          }
	  break;

	case IDV_CREATE:
	  /* We are creating the skeleton text of a new posting.
           * Most of the work is done by CreatePostingWnd and
           * CreatePostingText.  Here we have to identify
           * the newsgroup for those routines.
           * Get the newsgroup from the line in NetDoc that
           * points to this document.
           */
	  LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset,
		    ThisDoc->ParentLineID, &BlockPtr, &LinePtr);
	  ExtractTextLine (ThisDoc->ParentDoc, LinePtr,
			   mybuf, MAXINTERNALLINE);
	  UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
	  NewsgroupsPtr = mybuf;
	  CreatePostingWnd ((TypDoc *) NULL, DOCTYPE_POSTING);
	  break;
	}
      break;


    default:
      return (DefWindowProc (hWnd, message, wParam, lParam));
    }
  return (0);
}


/* --- function CopyNonBlank ----------------------------------------
 *
 *    Copy a string, terminating at the first blank or zero byte.
 *
 *    Entry    strtarg     FWA of target area.
 *             strsource   FWA of source string.
 *             maxchars    the maximum number of characters to copy
 *                         if no blank or zero is encountered.
 *
 *    Exit     returns the number of characters copied.
 *
 *    I believe this routine is no longer used, but I'm leaving it here.
 */

int
CopyNonBlank (strtarg, strsource, maxchars)
     char *strtarg, *strsource;
     int maxchars;
{
  int j;

  for (j = 0; j < maxchars && strsource[j] != ' '; j++)
    strtarg[j] = strsource[j];
  return (j);
}

/* --- function FileLength -------------------------------------------
 *
 *    Find the size, in bytes, of a file.
 *
 *    Entry    hFile    handle of the file in question.
 *
 *    Exit     returns the length of the file in bytes.
 *
 *    This routine is no longer used.
 */

long
FileLength (hFile)
     HANDLE hFile;
{
  long lCurrentPos = _llseek (hFile, 0L, 1);
  long lFileLength = _llseek (hFile, 0L, 2);

  _llseek (hFile, lCurrentPos, 0);

  return lFileLength;

}

/*-- function ViewArticle -------------------------------------------------
 *
 *  View a given article.   Either create a new window for it or
 *  recycle an existing window.
 *  This function requests an article from the server, so there
 *  must not already be a transaction in progress.
 *
 *    Entry    Doc            points to the document for this group.
 *	       artindex       index into header array for this group.
 *             Reuse          is TRUE if we ought to reuse the
 *                            currently active article window (if any).
 */
void
ViewArticle (Doc, artindex, Reuse)
     TypDoc *Doc;
     long artindex;
     BOOL Reuse;
{
  TypDoc *MyDoc;
  TypGroup far *GroupDoc;
  BOOL newdoc;
  BOOL found;
  int docnum;
  HANDLE hBlock;
  unsigned int Offset;
  TypLineID MyLineID;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  HWND hWndArt;
  int width;
  char mybuf[MAXINTERNALLINE];
  long int artnum,oldindex;
  char far *lpsz;
  HWND hWndGroup = Doc->hDocWnd;
  char far *lpszGroupName;
  HANDLE header_handle;
  TypHeader  * headers;

  LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID,
            &BlockPtr, &LinePtr);

  GroupDoc = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));

  header_handle = GroupDoc->header_handle;
  headers = (TypHeader  *) ((char *)GlobalLock (header_handle) + sizeof(char  *)) ;

  if (MyDoc = headers[artindex].ArtDoc)
    {

      /* We already have a document containing the article */
      /* so just activate it.                */

      /*  ShowWindow(MyDoc->hDocWnd,SW_SHOW); */
      SetActiveWindow (MyDoc->hDocWnd);
      SetFocus (MyDoc->hDocWnd);
      goto endit;
    }
  if (CommBusy)
    {
      MessageBox (hWndGroup, "Sorry, I am already busy retrieving information from the server.\n\
Try again in a little while.", "Can't request text of article", MB_OK | MB_ICONASTERISK);
      goto endit;
    }

  newdoc = FALSE;
  if ((NewArticleWindow && !Reuse) || !ActiveArticleDoc || !(ActiveArticleDoc->InUse))
    {
      found = FALSE;
      for (docnum = 0; docnum < MAXARTICLEWNDS; docnum++)
	{
	  if (!ArticleDocs[docnum].InUse)
	    {
	      found = TRUE;
	      newdoc = TRUE;
	      CommDoc = &(ArticleDocs[docnum]);
	      break;
	    }
	}
      if (!found)
	{
	  MessageBox (hWndGroup, "You have too many article windows \
active;\nClose one or uncheck the option \"New Window for each Article\".", "Can't open new window", MB_OK | MB_ICONASTERISK);
	  goto endit;
	}
    }
  else
    {
      /* Must reuse old window for this article.         */
      CommDoc = ActiveArticleDoc;
      if (CommDoc->ParentDoc == Doc) {
        oldindex = CommDoc->ParentOffset;	/* more slot abusage */
        headers[oldindex].ArtDoc = (TypDoc *) NULL;
      }
      /* clear out old doc */
      FreeDoc (CommDoc);
    }

  headers[artindex].Seen = TRUE;

  InvalidateRect (hWndGroup, NULL, FALSE);

  lpsz = (char far *) headers[artindex].subject;
  strcpy (mybuf, "Retrieving \"");
  lstrcat (mybuf, lpsz);
  lstrcat (mybuf, "\"");

  if (newdoc)
    {
      if (xScreen > 88 * CharWidth)
	{
	  width = 88 * CharWidth;
	}
      else
	{
	  width = xScreen - 1 * CharWidth;
	}
      hWndArt = CreateWindow ("WinVnArt",
			      mybuf,
			      WS_OVERLAPPEDWINDOW | WS_VSCROLL ,
			      xScreen - (width + (docnum) * CharWidth),	/* Initial X pos */
			    (int) (yScreen * 3 / 8) + (docnum) * LineHeight,	/* Initial Y pos */
			      (int) width,	/* Initial X Width */
			      (int) (yScreen * 5 / 8) - (1 * LineHeight),	/* Initial Y height */
			      NULL,
			      NULL,
			      hInst,
			      NULL);

      if (!hWndArt)
	return;			/* ??? */
#ifndef MAC
      ShowWindow (hWndArt, SW_SHOWNORMAL);
#else
      MyShowWindow (hWndArt, SW_SHOWNORMAL);
#endif
    }
  else
    {
      hWndArt = CommDoc->hDocWnd;
      SetWindowText (hWndArt, mybuf);
    }

  /*  Now that we have created the window, create the corresponding
   *  document, and make the new window active.
   */

  InitDoc (CommDoc, hWndArt, Doc, DOCTYPE_ARTICLE);

  CommDoc->InUse = TRUE;
  CommDoc->LastSeenLineID = artindex;	/* Keep an index with the article */

  SetActiveWindow (hWndArt);
  SetFocus (hWndArt);

  headers[artindex].ArtDoc = CommDoc;
  CommDoc->ParentOffset = artindex;
  InvalidateRect (hWndArt, NULL, FALSE);
  UpdateWindow (hWndArt);

  CommLinePtr = CommLineIn;
  CommBusy = TRUE;
  CommState = ST_ARTICLE_RESP;


  /* If we're not already in this group on the server,
   * send out a GROUP command for this window so we get back
   * into the right Group.
   */
  LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID,
	    &BlockPtr, &LinePtr);
  lpszGroupName = (char far *) LinePtr + sizeof (TypLine) + sizeof (TypGroup);
  if (lstrcmp (CurrentGroup, lpszGroupName))
    {
      CommState = ST_GROUP_REJOIN;
      strcpy (mybuf, "GROUP ");
      lstrcat (mybuf, lpszGroupName);
      /* lstrcat(mybuf,"\r"); */
      mylstrncpy (CurrentGroup, lpsz, MAXGROUPNAME);
      PutCommLine (mybuf, lstrlen (mybuf));
    }

  UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);

  artnum = headers[artindex].number;

  sprintf (mybuf, "ARTICLE %ld\r", artnum);
  PutCommLine (mybuf, lstrlen (mybuf));

endit:
	GlobalUnlock (header_handle);
}

void
view_article_by_message_id (Doc, article_request, artindex)
     TypDoc *Doc;
     char far * article_request;
     long artindex;
{
  TypDoc *MyDoc;
  TypGroup far *GroupDoc;
  BOOL newdoc;
  BOOL found;
  int docnum;
  HANDLE hBlock;
  unsigned int Offset;
  TypLineID MyLineID;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  HWND hWndArt;
  int width;
  char mybuf[MAXINTERNALLINE];
  long int artnum;
  char far *lpsz;
  HWND hWndGroup = Doc->hDocWnd;
  char far *lpszGroupName;
  HANDLE header_handle;
  TypHeader  * headers;

  LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID,
            &BlockPtr, &LinePtr);

  GroupDoc = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));

  header_handle = GroupDoc->header_handle;
  headers = (TypHeader  *) ((char *) GlobalLock (header_handle) + sizeof(char  *)) ;

  if (CommBusy)
    {
      MessageBox (hWndGroup, "Sorry, I am already busy retrieving information from the server.\n\
Try again in a little while.", "Can't request text of article", MB_OK | MB_ICONASTERISK);
      goto endit;
    }

  newdoc = FALSE;
  if ((NewArticleWindow) || !ActiveArticleDoc || !(ActiveArticleDoc->InUse))
    {
      found = FALSE;
      for (docnum = 0; docnum < MAXARTICLEWNDS; docnum++)
	{
	  if (!ArticleDocs[docnum].InUse)
	    {
	      found = TRUE;
	      newdoc = TRUE;
	      CommDoc = &(ArticleDocs[docnum]);
	      break;
	    }
	}
      if (!found)
	{
	  MessageBox (hWndGroup, "You have too many article windows \
active;\nClose one or uncheck the option \"New Window for each Article\".", "Can't open new window", MB_OK | MB_ICONASTERISK);
	  goto endit;
	}
    }
  else
    {
      /* Must reuse old window for this article.         */
      CommDoc = ActiveArticleDoc;
      /* sever the article/artindex connection */
      headers[CommDoc->ParentOffset].ArtDoc = (TypDoc *) NULL;
      /* clear out old doc */
      FreeDoc (CommDoc);
    }

  headers[artindex].Seen = TRUE;

  InvalidateRect (hWndGroup, NULL, FALSE);

  strcpy (mybuf, "Retrieving \"");
  lstrcat (mybuf, article_request);
  lstrcat (mybuf, "\"");

  if (newdoc)
    {
      if (xScreen > 88 * CharWidth)
	{
	  width = 88 * CharWidth;
	}
      else
	{
	  width = xScreen - 1 * CharWidth;
	}
      hWndArt = CreateWindow ("WinVnArt",
			      mybuf,
			      WS_OVERLAPPEDWINDOW | WS_VSCROLL ,
			      xScreen - (width + (docnum) * CharWidth),	/* Initial X pos */
			    (int) (yScreen * 3 / 8) + (docnum) * LineHeight,	/* Initial Y pos */
			      (int) width,	/* Initial X Width */
			      (int) (yScreen * 5 / 8) - (1 * LineHeight),	/* Initial Y height */
			      NULL,
			      NULL,
			      hInst,
			      NULL);

      if (!hWndArt)
	return;			/* ??? */
#ifndef MAC
      ShowWindow (hWndArt, SW_SHOWNORMAL);
#else
      MyShowWindow (hWndArt, SW_SHOWNORMAL);
#endif
    }
  else
    {
      hWndArt = CommDoc->hDocWnd;
      SetWindowText (hWndArt, mybuf);
    }

  /*  Now that we have created the window, create the corresponding
   *  document, and make the new window active.
   */

  InitDoc (CommDoc, hWndArt, Doc, DOCTYPE_ARTICLE);

  CommDoc->InUse = TRUE;
  CommDoc->LastSeenLineID = artindex;	/* Keep an index with the article */

  SetActiveWindow (hWndArt);
  SetFocus (hWndArt);

  headers[artindex].ArtDoc = CommDoc;
  InvalidateRect (hWndArt, NULL, FALSE);
  UpdateWindow (hWndArt);

  CommLinePtr = CommLineIn;
  CommBusy = TRUE;
  CommState = ST_ARTICLE_RESP;


  /* If we're not already in this group on the server,
   * send out a GROUP command for this window so we get back
   * into the right Group.
   */
  LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID,
	    &BlockPtr, &LinePtr);
  lpszGroupName = (char far *) LinePtr + sizeof (TypLine) + sizeof (TypGroup);
  if (lstrcmp (CurrentGroup, lpszGroupName))
    {
      CommState = ST_GROUP_REJOIN;
      strcpy (mybuf, "GROUP ");
      lstrcat (mybuf, lpszGroupName);
      /* lstrcat(mybuf,"\r"); */
      mylstrncpy (CurrentGroup, lpsz, MAXGROUPNAME);
      PutCommLine (mybuf, lstrlen (mybuf));
    }

  UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);

  artnum = headers[artindex].number;

  lstrcpy (mybuf, article_request);
  PutCommLine (mybuf, lstrlen (mybuf));

endit:
	GlobalUnlock (header_handle);
}

/*-- Function UnlinkArtsInGroup ---------------------------------------
 *
 *  Modify all the article documents and all the article windows currently
 *  associated with a group so that none of them points to that group.
 *  Used when the group window is going away or is being recycled.
 *
 *    Entry    GroupDoc    points to the document to which references
 *                         should be eliminated.
 */
void
UnlinkArtsInGroup (GroupDoc)
     TypDoc *GroupDoc;
{
  int iart;

  for (iart = 0; iart < MAXARTICLEWNDS; iart++)
    {
      if (ArticleDocs[iart].InUse && ArticleDocs[iart].ParentDoc == GroupDoc)
	{
	  ArticleDocs[iart].ParentDoc = (TypDoc *) NULL;
	  ArticleDocs[iart].hParentBlock = 0;
	}
    }
}

/*--- function UpdateSeenArts -------------------------------------------
 *
 *  Given a Group document, update the TypGroup line for
 *  that document in the Net document with respect to which
 *  articles have been seen.
 *  This routine would typically be called just before a Group document
 *  is going to be destroyed or erased.  That would be the time to
 *  take the information in the TypArticle structures of each line
 *  in the document and transfer it to the line in the NetDoc document
 *  corresponding to this group.
 *
 *  This routine has to take information of the form:
 *    123:Unseen;  124:Seen; 125:Unseen; 126:Unseen; 127:Seen; 128:Seen; 129:Seen
 *  found in the TypArticle structures in consecutive lines in the document
 *  and transform it to the general form used by .newsrc files:
 *    124,127-129
 *  (though we are using our internal representation & not ASCII characters).
 *
 *    Entry    Doc      points to the document for this group.
 *
 *    Exit     The line in the Net document corresponding to this
 *              group has been updated.
 */
void
UpdateSeenArts (Doc)
     TypDoc *Doc;
{
  TypRange MyRange, *RangePtr;
  TypGroup *group;
  TypLine far *LinePtr, far * ParentLine;
  TypBlock far *BlockPtr, far * ParentBlock;
  HANDLE hLine,header_handle;
  TypLine *LocalLinePtr;
  TypHeader  * headers;
  TypArticle far *Art;
  BOOL InSeen = TRUE;
  unsigned int MyLength;
  unsigned int maxRanges;
  long artindex;

  /*  Get the line in the Net document that corresponds to this
   *  group.  Make a local copy of it and set RangePtr to point to
   *  the first range in that line.  We will ignore the old line's
   *  "seen" data and create the information afresh from what we
   *  have in this document.
   */
  LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID,
	    &ParentBlock, &ParentLine);

  hLine = LocalAlloc (LMEM_MOVEABLE, BLOCK_SIZE);
  LocalLinePtr = (TypLine *) LocalLock (hLine);
  group = (TypGroup *) ((char *) LocalLinePtr + sizeof (TypLine));

  MoveBytes (ParentLine, LocalLinePtr, ParentLine->length);

  header_handle = group->header_handle;
  headers = (TypHeader  *) ((char *) GlobalLock (header_handle) + sizeof (char  *));

  group->nRanges = 0;

  maxRanges = ((Doc->BlockSize - Doc->SplitSize) - ParentLine->length +
	       group->nRanges * sizeof (TypRange)) / sizeof (TypRange) - 1;

  RangePtr = (TypRange *) ((char *) LocalLinePtr + sizeof (TypLine) +
			   RangeOffset (group->NameLen));
  MyRange.First = 1;

  /* Get the first line in this document.
   * If it cannot be found, just set Last=First and skip the
   * proceeding processing.  Otherwise, assume we've seen everything
   * up to but not including the first article in the document.
   */

/*  LockLine (Doc->hFirstBlock, sizeof (TypBlock), 0L, &BlockPtr, &LinePtr); */

  artindex = 0;
  if (!Doc->TotalLines)
    {
      MyRange.Last = 1;
    }
  else
    {
      MyRange.Last = headers[artindex].number - 1;

      /* Loop to scan through the document, fabricating article ranges.
       */
      do
	{
	  if (headers[artindex].Seen)
	    {
	      if (InSeen)
		{
		  /* Continuing a sequence of seen articles.            */
		  MyRange.Last = headers[artindex].number;
		}
	      else
		{
		  /* Starting a new sequence of seen articles.          */
		  MyRange.First = headers[artindex].number;
		  MyRange.Last = headers[artindex].number;
		  InSeen = TRUE;
		}
	    }
	  else
	    {
	      if (InSeen)
		{
		  /* Ending a sequence of seen articles.                   */
		  InSeen = FALSE;
		  *(RangePtr++) = MyRange;
		  (group->nRanges)++;
		}
	      else
		{
		  /* Continuing a sequence of unseen articles.             */
		}
	    }
	}
	while ((group->nRanges < maxRanges) &&
	       ((++artindex < Doc->TotalLines)));

      if (InSeen)
	{
	  *(RangePtr++) = MyRange;
	  (group->nRanges)++;
	}
    }

	MyLength = sizeof (TypLine) + RangeOffset (group->NameLen) +
           sizeof (TypRange) * (group->nRanges) + sizeof (int);

  LocalLinePtr->length = MyLength;
  *(int *) ((char *) LocalLinePtr + MyLength - sizeof (int)) = MyLength;

  ReplaceLine (LocalLinePtr, &ParentBlock, &ParentLine);
  GlobalUnlock (ParentBlock->hCurBlock);

  LocalUnlock (hLine);
  LocalFree (hLine);

}


/*-- function CursorToTextLine ----------------------------------------
 *
 *   Routine to locate a text line in a document, based on the
 *   cursor position.  Used to figure out which line is being selected
 *   when a user clicks a mouse button.
 *
 *   Entry    X, Y    are the position of the cursor.
 *            DocPtr  points to the current document.
 *
 *   Exit     *LinePtr points to the current line, if one was found.
 *            *BlockPtr points to the current block, if found.
 *            Function returns TRUE iff a line was found that corresponds
 *              to the cursor position.
 */
long
NewCursorToTextLine (X, Y, DocPtr)
     int X;
     int Y;
     TypDoc *DocPtr;
{
  int found;
  int SelLine;

  if (Y < TopSpace || Y > TopSpace + DocPtr->ScYLines * LineHeight ||
      X < SideSpace)
    {
      /* Cursor is in no-man's-land at edge of window.               */
	return(-1);
    }
  else
    {
      SelLine = (Y - TopSpace) / LineHeight;
	return ( DocPtr->TopLineOrd + SelLine);
    }
}

long
search_headers (TypDoc * HeaderDoc,
                TypHeader  *headers,
                long artindex,
                long num_headers)
{

	do {
	if (string_compare_insensitive (headers[artindex].subject,
                                        HeaderDoc->SearchStr))
	       return (artindex);		/* return the index */
	} while (artindex++ < (num_headers - 1));

	return (-1);			/* not found */
}

LPSTR
string_compare_insensitive (LPSTR a, LPSTR b)
{
  int lena = lstrlen (a);
  int lenb = lstrlen (b);
  int count;

  for ( count = lena - lenb + 1 ; count > 0 ; count--, a++)
   if (_strnicmp (a,b,lenb) == 0)
     return a;

  return (NULL);
}
