/*
 * $Id: wvlist.c 1.35 1997/02/06 17:27:19 dumoulin Exp $
 */

/*-- WVLIST.C -- File containing functions to deal with the NNTP LIST
 *   command, which lists all the newsgroups and their status.
 *
 *   Mark Riordan   25 October 1990
 */

#include <windows.h>
#include <windowsx.h>
#include "wvglob.h"
#include "winvn.h"
#pragma hdrstop
#include "dos.h"
#include "primes.h"
#include "defaults.h"

#include <ctype.h>
#include <assert.h>


/* The size of the chunks of memory that make up the new group list data */
#define CHUNKSIZE       64L*1024L

TypLine huge * huge * NetHashTable;
HANDLE hNetHashTable  = NULL;

HANDLE htohNewGroupLines;
HANDLE far *lphNewGroupLines; /* array of handles to new group lines */

DWORD NewGroupDataSize;     /* The size (in bytes) of NewGroupData */
char huge *CurrentSpot;     /* The pointer to the next available spot in NewGroupData */
unsigned long BytesSoFar;

/* Ray Dunn:99/10: cast to avoid compiler warning */
#define MyGlobalUnlock(hWhat)  MRRGlobalUnlock(hWhat, (WORD)__LINE__)
/* Ray Dunn:99/10:End */


WORD MRRGlobalUnlock (HANDLE hWhat, WORD wLine);

WORD MRRGlobalUnlock (HANDLE hWhat, WORD )
{
	WORD LockCount = 0;

	if (hWhat) 
	{
		//only used in 16bit windows
		if (!(LockCount = (GMEM_LOCKCOUNT & GlobalFlags (hWhat)))) 
		{
		}
		else 
		{
			GlobalUnlock (hWhat);
		}
	}
	return LockCount; 
}


BOOL FreeHashTable()
{

	//We'll, we need a new and different hash table now, so close out the old one
	if (hNetHashTable)
	{
	
		GlobalUnlock (hNetHashTable);
		GlobalFree (hNetHashTable);
		hNetHashTable = NULL;
		return TRUE;
	}
	return FALSE;

}

/*--- function StartList -----------------------------------------------
 *
 *  Initiate the process of sending a LIST command and using its
 *  output to update our list of news groups.
 */

void StartList ()
{

	if ( Initializing == INIT_NOT_CONNECTED && Autoconnect && !Reconnecting)
	{
		if (!InlineReconnect(NetDoc.hDocWnd))
			return ;
	}


	CommState = ST_LIST_RESP;
	CommBusy = TRUE;
	Initializing = INIT_SCANNING_NETDOC;
	InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
	SendMessage (NetDoc.hWndFrame, WM_PAINT, 0, 0L);

	/* Set up table of pointers to new group lines. */
	InitGroupTable ();

	/* Initialize the hash table */
	InitHashTable (LinesInRC);

	/* And set up the hash table for stuff from the newsrc */
	HashNetGroups (&NetDoc, NetHashTable);

	//now start list
	if (!PutCommLine ("LIST"))
	{
		if ( Autoconnect && Reconnecting)
		{
			if (!InlineReconnect(NetDoc.hDocWnd))
				return ;
			PutCommLine ("LIST");
		}
	}


	InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
	Initializing = INIT_GETTING_LIST;
	SendMessage (NetDoc.hWndFrame, WM_PAINT, 0, 0L);
}

/*--- function InitHashTable -------------------------------------------
 *
 *  Allocate and zero the NetHashTable. The argument is the number of
 *  elements to be hashed (NOT the number of elements in the hash table)
 *
 *  Exit:   hNetHashTable     is the handle
 *          NetHashTable      points to the table
 *                      HashTableSize     The number of elements in the hash table
 */
void InitHashTable (unsigned long Elements)
{

	/* First, figure out how large the hashtable should be.
	* The basic algorithm used here is to double the size of what you're
	* hashing, rounded up to the next nearest prime number
	*/
	HashTableSize = (unsigned long) FindNextPrime (Elements * 2L);

	assert(hNetHashTable == NULL);

	if (hNetHashTable)
	{
		FreeHashTable();

	}

	
	/* Set up hash table for group names */
	hNetHashTable = GlobalAlloc (GHND,
				 HashTableSize *
				 sizeof (TypLine far *));
	NetHashTable = (TypLine huge * huge *) GlobalLock (hNetHashTable);

}


/*--- function InitGroupTable -------------------------------------------
 *
 *  Allocate the first CHUNKSIZE bytes of memory for the
 * new group lines.
 *
 *  Exit:   hNewGroupData    is the handle
 *          NewGroupData     points to the table
 *          nNewGroups        has been initialized to 0.
 *                      NewGroupDataSize  is the current size of the NewGroupData
 */
void InitGroupTable (void)
{
	/* Allocate the first chunk */
	NewGroupDataSize = CHUNKSIZE;
	hNewGroupData = GlobalAlloc (GHND, NewGroupDataSize);
	NewGroupData = (void far *) GlobalLock (hNewGroupData);
	CurrentSpot = (char *) NewGroupData;
	BytesSoFar = 0;
	nNewGroups = 0L;
}


/*--- function HashNetGroups ------------------------------------------
 *
 *  Enter all the groups in the Net document into the hash table.
 *
 *  Exit    All blocks in the document are locked.
 *          HashTable   is (partially) filled with pointers to
 *                      each of the lines in "Doc".
 */

void HashNetGroups (TypDoc *Doc, TypLine huge * huge * HashTable)
{
	TypBlock far *BlockPtr;
	TypLine far *LinePtr;
	unsigned long hashval;
	HANDLE hBlock;
	unsigned int Offset;
	unsigned int TextOffset;
	unsigned char far *textptr;
	TypLineID MyLineID;

	/* Lock all blocks in the document */
	hBlock = Doc->hFirstBlock;
	do 
	{
		BlockPtr = (TypBlock far *) GlobalLock (hBlock);
		hBlock = BlockPtr->hNextBlock;
	} while (hBlock);

	/* Now start at the beginning of the document, going through
	* each line in the document, hashing its group name into the table.
	*/

	hBlock = Doc->hFirstBlock;
	Offset = sizeof (TypBlock);
	MyLineID = 0L;

	LockLine (hBlock, Offset, MyLineID, &BlockPtr, &LinePtr);

	TextOffset = Doc->OffsetToText;

	if (LinePtr->length != END_OF_BLOCK) 
	{
		do 
		{
			textptr = ((unsigned char far *) LinePtr) + TextOffset;
			hashval = HashGroup (textptr);
			while (HashTable[hashval]) 
			{
				hashval = (hashval + 1) % HashTableSize;
			}
			HashTable[hashval] = LinePtr;
		}while (NextLine (&BlockPtr, &LinePtr));
	}
	UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
}

/*--- function ProcListLine ---------------------------------------------
 *
 *  Process a line received from the NNTP LIST command output.
 *  Each line from the LIST command has the form:
 *   <groupname> <last_art_#> <first_art_#> {y|n|m}
 *  y=allow post  n=disallow post m=moderated
 *  The <first_art_#> and <last_art_#> fields will always be numeric.  They may have
 *  leading zeros.  If the <last_art_#> field evaluates to less than the
 *  <first_art_#> field, there are no articles currently on file in the
 *  newsgroup
 */

void ProcListLine (unsigned char *ListLine)
{
	unsigned long hashval;
	char far *textptr;
	unsigned char *cptr, *restline;
	char mygroupline[BLOCK_SIZE];
	TypGroup *mygroup;
	TypGroup far *netgroup;
	UBIGINT ArtNum;
	int PercentDone;

	if ((++RcvLineCount) % UPDATE_TITLE_FREQ == 0) 
	{
		InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
		if (NetDoc.TotalLines > 0) 
		{
			//  PercentDone = MulDiv (RcvLineCount, 100, NetDoc.TotalLines);
			PercentDone = (int) ((RcvLineCount * 100) / NetDoc.TotalLines);
			if (PercentDone <= 100)
				SetStatbarPercent (NetDoc.hWndFrame, PercentDone, &NetDoc, TRUE);
		}
		if (RcvLineCount % (UPDATE_TITLE_FREQ * 25) == 0) 
		{
			UpdateWindow (NetDoc.hDocWnd);
		}
	}

	/* Replace the first blank in the input line with a zero. */
	for (cptr = ListLine; *cptr && *cptr != ' '; cptr++);
		*cptr = '\0';
	restline = cptr + 1;      /* points to highest art # */

	//MDS toss out long groups that we can't handle 19991210
	if (strlen( (char*) ListLine) >= MAXGROUPNAME)
		return;

	hashval = HashGroup (ListLine);
checkhash:;
	if (!NetHashTable[hashval]) 
	{
		/* This is a new group.
		* Create a Group line from the information in this line.
		*/

		ListLine += BuildGroup( (char*)ListLine, (TypLine *) (mygroupline));
		mygroup = (TypGroup *) (mygroupline + sizeof (TypLine));
		GetNum ((char**)&restline, &(mygroup->ServerLast));
		GetNum ((char **)&restline, &(mygroup->ServerFirst));
		if (mygroup->ServerLast < mygroup->ServerFirst)
		{
			mygroup->ServerLast = mygroup->ServerFirst;
			mygroup->ServerEstNum = 0;
		}
		else
			mygroup->ServerEstNum = mygroup->ServerLast - mygroup->ServerFirst + 1;
		mygroup->Determined = TRUE;
		AddGroupToTable (mygroupline);
	}
	else 
	{
		textptr = ((char far *) NetHashTable[hashval] + sizeof (TypLine) + sizeof (TypGroup));
		if (lstrcmp (textptr, (char*)ListLine)) 
		{
			hashval = (hashval + 1) % HashTableSize;
			goto checkhash;
		}
		else 
		{

			/* This group is already present in NetDoc.
			   Update the ServerFirst and ServerLast fields.*/
			netgroup = (TypGroup far *) ((char far *) NetHashTable[hashval] + sizeof (TypLine));
			GetNum ((char **)&restline, &ArtNum);
			netgroup->ServerLast = ArtNum;
			GetNum ((char **)&restline, &ArtNum);
			netgroup->ServerFirst = ArtNum;
			if (netgroup->ServerLast+1 < netgroup->ServerFirst)
				netgroup->ServerEstNum = 0;
			else
				netgroup->ServerEstNum = (netgroup->ServerLast - netgroup->ServerFirst + 1);
			netgroup->Determined = TRUE;
			netgroup->NumUnread = CalcNumUnread(netgroup);
		}
	}
}

/*--- function AddGroupToTable ----------------------------------------
 *
 *  Add a group line, formatted for eventual inclusion in NetDoc,
 *  to NewGroupTable.
 *
 *  Entry:  GroupLine      is the line to add to the table.
 *
 *  Exit:   NewGroupTable  contains the line
 *          nNewGroups     has been incremented.
 */
BOOL AddGroupToTable (char far * GroupLine)
{
	char huge *NextPtr;
	char huge *EndPtr;

	NextPtr = (char huge *) CurrentSpot + ((TypLine far *) GroupLine)->length;
	EndPtr = (char huge *) NewGroupData + NewGroupDataSize;

	/* Check to see if the current data will put us over the edge */
	if (NextPtr > EndPtr) 
	{
		GlobalUnlock (hNewGroupData);
		//
		HANDLE hNewData;
		hNewData = GlobalReAlloc (hNewGroupData, NewGroupDataSize+CHUNKSIZE,GMEM_MOVEABLE);
		if (!hNewData)
		{
			//memory fail
			NewGroupData = GlobalLock (hNewGroupData);
			return FALSE;
		}
		NewGroupDataSize += CHUNKSIZE;
		hNewGroupData = hNewData;
		NewGroupData = GlobalLock (hNewGroupData);
		CurrentSpot = (char huge *) NewGroupData + BytesSoFar;
		NextPtr = (char huge *) CurrentSpot + ((TypLine far *) GroupLine)->length;
	}

	/* Create a copy of this line in far memory
	*/
	MoveBytes ((char far *) GroupLine, CurrentSpot,
				((TypLine far *) GroupLine)->length);
	nNewGroups++;

	BytesSoFar += ((TypLine huge *) CurrentSpot)->length;
	CurrentSpot = NextPtr;
	return TRUE;

}

/*--- function HashGroup -------------------------------------------------
 *
 *  Hash a string into an unsigned integer.
 *
 *  This hash function is designed based on information from a
 *  handout for a UC Berkeley class CS 60C, Spring 1990, Clancy/
 *  Harrison.  I picked up the handout while wandering around on
 *  Berkeley's campus in May 1990.  The handout in turn is based
 *  on the McKenzie, et al., article "Selecting a Hashing Algorithm"
 *  in Software Practice and Experience, Vol 20, no 2, Feb 1990.
 *  The algorithm is similar to that used in the AT&T C++ compiler.
 *  /mrr
 */

unsigned long HashGroup (unsigned char huge * gname)
{
	long unsigned int sum = 0;
	unsigned long hash;

	for (; *gname; gname++) 
	{
		sum = (sum << 1) + *gname;
	}
	hash = (sum % HashTableSize);
	return (hash);
}



/*--- function BuildPtrList ------------------------------------------
 *
 *  Build both the array of pointers into the new group data, as well
 *  as build the hashtable.
 */
void BuildPtrList ()
{
	unsigned long i;

	/* Build the pointer array after the fact */
	if (nNewGroups != 0) 
	{
		hNewGroupTable = GlobalAlloc (GHND, nNewGroups * sizeof (TypLine huge *));
		NewGroupTable = (void huge * huge *) GlobalLock (hNewGroupTable);
		if (NewGroupTable == NULL)
			for (i = 0; i < 10; i++) 
			{
				NewGroupTable = (void huge * huge *) GlobalLock (hNewGroupTable);
				if (NewGroupTable) 
					break;
			}

		if (NewGroupTable == NULL)
		{
			WVMessageBox (NetDoc.hDocWnd, IDS_WVLIST_MEM_LOCK, IDS_WVLIST_LOCK, MB_OK | MB_ICONEXCLAMATION);                       
		}
		else
		{
			CurrentSpot = (char*)NewGroupData;

			for (i = 0; i < nNewGroups; i++) 
			{
				NewGroupTable[i] = CurrentSpot;
				CurrentSpot += ((TypLine huge *) CurrentSpot)->length;
			}
		}
	}
}

/*--- function BuildHashTable ------------------------------------------
 *
 *  Build the hashtable from the NewGroupTable.
 */
void BuildHashTable ()
{
	unsigned long i,hashval;
	unsigned char *textptr;

	for (i = 0; i < nNewGroups; i++) 
	{
		/* Plus fill in the hash table */
		textptr = (unsigned char  *) NewGroupTable[i] + sizeof (TypLine)
				+ sizeof (TypGroup);
		hashval = HashGroup (textptr);
		while (NetHashTable[hashval]) 
		{
			hashval = (hashval + 1) % HashTableSize;
		}
		NetHashTable[hashval] = (struct structline *) NewGroupTable[i];
	}

}

//
//
// 

TypLine * FindHashEntry( char * name)
{
	unsigned long hashval = HashGroup ( (unsigned char *) name);

	while (NetHashTable[hashval])
	{	
		char far * textptr = ((char far *) NetHashTable[hashval] + sizeof (TypLine) + sizeof (TypGroup));
		if (lstrcmp (textptr, (char*)name)) 
			hashval = (hashval + 1) % HashTableSize;
		else
			return (TypLine*) NetHashTable[hashval];  //Found entry.

	}

	return NULL;

}


/*--- function ProcEndList -------------------------------------------
 *
 *  Do the final processing when we have reached the end of the
 *  list of newsgroups sent us via the LIST command.
 */

void ProcEndList ()
{
	DLGPROC lpfnWinVnGroupListDlg;
	WORD LockCount;

	/* Well, we need a new and different hash table now, so close out the
	 old one */
	LockCount = GMEM_LOCKCOUNT & GlobalFlags (hNetHashTable);
	FreeHashTable();

	InitHashTable (nNewGroups);
	BuildPtrList ();
	BuildHashTable ();

	if (1) { //SortGroupList) {		//MDS always sort new group list otherwise you get multiple duplicate top level heirarchies
#if 1
	ShellSort (NewGroupTable, nNewGroups, sizeof (void huge *), (int (*)(void const huge *, void const huge *))GroupCompare);
#else
	ShellSort (NewGroupTable, nNewGroups, sizeof (void huge *), GroupCompare);
#endif
	}

	lpfnWinVnGroupListDlg = &WinVnGroupListDlg;//(DLGPROC) MakeProcInstance ((FARPROC) WinVnGroupListDlg, hInst);

	/* Display dialog box of new groups.  */
	if (nNewGroups)
		if (DialogBox (hInst, MAKEINTRESOURCE(IDD_WINVNGROUPLIST), NetDoc.hDocWnd, lpfnWinVnGroupListDlg)) 
		{
		//    if ((nNewGroups > DEF_MaxNewGroups) || 
		//        DialogBox (hInst, MAKEINTRESOURCE(IDD_WINVNGROUPLIST), NetDoc.hDocWnd, lpfnWinVnGroupListDlg)) {
		/* The user has clicked OK or the number of new groups is too large for the
		* user to bother with, so add all the new groups to the
		* Net document.  Subscribed groups go at the end of the Subscribed
		* section at the top of the doc.
		* Unsubscribed groups go in the section below, in alphabetical order.
		*/
		MergeGroups (ADD_SUBSCRIBED_END_OF_SUB);
		NetDoc.LongestLine = 0;
		SetGroupActiveLines ();
	}

	/* Unlock and/or free memory in NetDoc and NewGroupTable.  */
	CleanUpGroupTable ();

	/* Unlock and free the hash table.   */
	LockCount = GMEM_LOCKCOUNT & GlobalFlags (hNetHashTable);
	FreeHashTable();
	ScreenToTop (&NetDoc); // Fix NetDoc.TopScLineID
	InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
	SetNetDocTitle ();
	NewsrcDirty = TRUE;
}

/*--- function GroupCompare --------------------------------------------
 *
 *  Compare two group lines alphabetically by group name.
 */
int	GroupCompare (TypLine const huge * huge * g1, TypLine const huge * huge * g2)
{
	char huge *gch1, huge * gch2;

	if ((g1 != NULL) && (g2 != NULL)) 
	{
		gch1 = (char huge *) *g1 + sizeof (TypLine) + sizeof (TypGroup);
		gch2 = (char huge *) *g2 + sizeof (TypLine) + sizeof (TypGroup);
		return (lstrcmp (gch1, gch2)); 
	}
	else
		return (0);
}


/*-- function WinVnGroupListDlg ---------------------------------------
 *
 *  Dialog function to handle selection of new newsgroups to
 *  subscribe to.  (I know, don't end a sentence with "to".)
 */

INT_PTR CALLBACK WinVnGroupListDlg (HWND hDlg, unsigned iMessage, WPARAM wParam, LPARAM lParam)
{
	unsigned long j,hashval;
	LRESULT nItem;
	char TmpBuf[80];
	char far *CurName = "";
	char huge *cptr;
	TypHier far *CurHier = NULL;
	static HANDLE hStartHier, hCurHier;
	TypGroup huge *group;
	HWND hDlgHierList;      /* Handle to child hierarchy list box window. */
	HWND hDlgSubList;
	HWND hDlgUnSubList;
	HDC  hListboxDC;
	static int maxSubExt, maxUnsubExt;
#ifdef _WIN32
	SIZE size;
#else
	DWORD size;
#endif  
	int ext; 
	switch (iMessage) 
	{

	case WM_INITDIALOG:
		/* First build the list of top-level hierarchies */
		CurHier = (TypHier *) NULL;
		hStartHier = hCurHier = (HANDLE) NULL;
		maxSubExt = maxUnsubExt = 0;
		for (j = 0; j < nNewGroups; j++) 
		{
			cptr = 0;
			cptr = (char *)(NewGroupTable[j]);
			cptr += sizeof (TypLine) + sizeof (TypGroup);
			if ((CurHier == NULL) ||
				(strnicmp (CurName, cptr, strcspn (cptr, ".")) != 0)) 
			{
				HANDLE hTmpHandle;
				int NameLen;
				/* Need a new link */
				hTmpHandle = GlobalAlloc (GHND, sizeof (TypHier));
				if (CurHier) 
				{
					CurHier->hNext = hTmpHandle;
					GlobalUnlock (CurHier->hName);
					GlobalUnlock (hCurHier);
					hCurHier = hTmpHandle;
					CurHier = (TypHier far *) GlobalLock (hCurHier);
				}
				else 
				{
				  hCurHier = hStartHier = hTmpHandle;
				  CurHier = (TypHier far *) GlobalLock (hCurHier);
				}
				/* And now for the actual hierarchy name */
				NameLen = strcspn (cptr, ".");
				CurHier->hName = GlobalAlloc (GMEM_MOVEABLE, NameLen + 1);
				CurName = (char *) GlobalLock (CurHier->hName);
				strntcpy (CurName, cptr, NameLen);
				/* And the start index */
				CurHier->Start = j;
			}
			/* Increment the number of groups _every_ time */
			CurHier->NumGroups++;
		}
	GlobalUnlock (CurHier->hName);
	GlobalUnlock (hCurHier);

	/* Now, load the HIERARCY box */
	hDlgHierList = GetDlgItem (hDlg, IDD_HIERARCHY_LISTBOX);
	SendMessage (hDlgHierList, WM_SETREDRAW, FALSE, 0L);
	hCurHier = hStartHier;
	while (hCurHier) 
	{
		HANDLE hTmpHandle;
		CurHier = (TypHier far *) GlobalLock (hCurHier);
		CurName = (char *)GlobalLock (CurHier->hName);
		SendMessage (hDlgHierList, LB_ADDSTRING, 0, (LPARAM) CurName);
		hTmpHandle = CurHier->hNext;
		GlobalUnlock (CurHier->hName);
		GlobalUnlock (hCurHier);
		hCurHier = hTmpHandle;
	}
	SendMessage (hDlgHierList, WM_SETREDRAW, TRUE, 0L);

	/* And select the 0th item to kick things off */
	SendMessage (hDlgHierList, LB_SETCURSEL, 0, 0);
#ifdef _WIN32
	SendMessage (hDlg, WM_COMMAND, (WPARAM) MAKEWPARAM (IDD_HIERARCHY_LISTBOX,
		LBN_SELCHANGE), (LPARAM) GetDlgItem (hDlg, IDD_HIERARCHY_LISTBOX));
#else
  SendMessage (hDlg, WM_COMMAND, IDD_HIERARCHY_LISTBOX,
        (	LPARAM) MAKELPARAM (GetDlgItem (hDlg, IDD_HIERARCHY_LISTBOX),
                   LBN_SELCHANGE));
#endif /* _WIN32 */
  return TRUE;
  break;

  case WM_COMMAND:
		switch (LOWORD (wParam)) 
		{
		case IDOK:
			EndDialog (hDlg, TRUE);
			break;
		case IDCANCEL:
			EndDialog (hDlg, FALSE);
			break;
		case IDD_SUBSCRIBED_GROUP_LISTBOX:
			switch (GET_WM_COMMAND_CMD(wParam, lParam))
			{
				case LBN_DBLCLK:
					/* switch to unsubscribed */
					nItem = SendMessage (GET_WM_COMMAND_HWND(wParam, lParam), LB_GETCURSEL, 
					(WPARAM)0, (LPARAM)0);
					SendMessage (GET_WM_COMMAND_HWND(wParam, lParam), LB_GETTEXT, 
					(WPARAM)nItem, (LPARAM)TmpBuf);
					SendMessage (GET_WM_COMMAND_HWND(wParam, lParam), LB_DELETESTRING, 
					(WPARAM)nItem, (LPARAM)0);

					/* Now pull the stucture out of the hash table */
					hashval = HashGroup ((unsigned char *)TmpBuf);
					while (strcmp ((char huge *) NetHashTable[hashval] + sizeof (TypLine) + sizeof (TypGroup),
								TmpBuf) != 0) 
					{
						hashval = (hashval + 1) % HashTableSize;
					}
					group = (TypGroup huge *) ((char huge *) NetHashTable[hashval] + sizeof (TypLine));
					group->Subscribed = 0;

					SendMessage (GetDlgItem (hDlg, IDD_UNSUBSCRIBED_GROUP_LISTBOX),
					LB_ADDSTRING, 0, (LPARAM) TmpBuf);

					hListboxDC = GetDC(GetDlgItem (hDlg, IDD_UNSUBSCRIBED_GROUP_LISTBOX));
#ifdef _WIN32
					GetTextExtentPoint32(hListboxDC, TmpBuf, strlen(TmpBuf), &size);
					ext = (int)size.cx;
#else
					size = GetTextExtent(hListboxDC, TmpBuf, strlen(TmpBuf));
					ext = (int)LOWORD(size);
#endif
					if (ext > maxUnsubExt) 
					{ 
						maxUnsubExt = ext;
						SendDlgItemMessage(hDlg, IDD_UNSUBSCRIBED_GROUP_LISTBOX, 
						LB_SETHORIZONTALEXTENT,(WPARAM)maxUnsubExt,(LPARAM)0);
					}
					ReleaseDC(GetDlgItem (hDlg, IDD_UNSUBSCRIBED_GROUP_LISTBOX), hListboxDC);
					break;
				default:
					return FALSE;

			}
			break;
		case IDD_UNSUBSCRIBED_GROUP_LISTBOX:
			switch (GET_WM_COMMAND_CMD(wParam, lParam))
			{
				case LBN_DBLCLK:
					/* switch to subscribed */
					nItem = SendMessage (GET_WM_COMMAND_HWND(wParam, lParam), LB_GETCURSEL, (WPARAM)0, (LPARAM)0);
					SendMessage (GET_WM_COMMAND_HWND(wParam, lParam), LB_GETTEXT, (WPARAM)nItem, (LPARAM)TmpBuf);
					SendMessage (GET_WM_COMMAND_HWND(wParam, lParam), LB_DELETESTRING, (WPARAM)nItem, (LPARAM)0);
					/* Now pull the stucture out of the hash table */
					hashval = HashGroup ((unsigned char *)TmpBuf);
					while (strcmp ((char huge *) NetHashTable[hashval] +
							 sizeof (TypLine) + sizeof (TypGroup),
							 TmpBuf) != 0) 
					{
						hashval = (hashval + 1) % HashTableSize;
					}
					group = (TypGroup huge *) ((char huge *) NetHashTable[hashval] + sizeof (TypLine));
					group->Subscribed = 1;

					SendMessage (GetDlgItem (hDlg, IDD_SUBSCRIBED_GROUP_LISTBOX), LB_ADDSTRING, 0, (LPARAM) TmpBuf);
					hListboxDC = GetDC(GetDlgItem (hDlg, IDD_SUBSCRIBED_GROUP_LISTBOX));
#ifdef _WIN32
					GetTextExtentPoint32(hListboxDC, TmpBuf, strlen(TmpBuf), &size);
					ext = (int)size.cx;
#else
					size = GetTextExtent(hListboxDC, TmpBuf, strlen(TmpBuf));
					ext = (int)LOWORD(size);
#endif
					if (ext > maxSubExt) 
					{ 
						maxSubExt = ext;
						SendDlgItemMessage(hDlg, IDD_SUBSCRIBED_GROUP_LISTBOX, LB_SETHORIZONTALEXTENT,(WPARAM)maxSubExt,(LPARAM)0);
					}
					ReleaseDC(GetDlgItem (hDlg, IDD_SUBSCRIBED_GROUP_LISTBOX), hListboxDC);
					break;
				default:
					return FALSE;

			}
			break;
			
			case IDD_HIERARCHY_LISTBOX:
				switch (GET_WM_COMMAND_CMD(wParam, lParam))
				{
					case LBN_SELCHANGE:
					/* Find the info for this hierarchy */
					nItem = SendMessage (GET_WM_COMMAND_HWND(wParam, lParam), LB_GETCURSEL, 
					  (WPARAM)0, (LPARAM)0);
					SendMessage (GET_WM_COMMAND_HWND(wParam, lParam), LB_GETTEXT, 
					  (WPARAM)nItem, (LPARAM)TmpBuf);

					hCurHier = hStartHier;
					while (hCurHier) 
					{
						HANDLE hTmpHandle;
						CurHier = (TypHier far *) GlobalLock (hCurHier);
						CurName = (char *)GlobalLock (CurHier->hName);
						GlobalUnlock (CurHier->hName);
						if (stricmp (CurName, TmpBuf) == 0)
							break;
						hTmpHandle = CurHier->hNext;
						GlobalUnlock (hCurHier);
						hCurHier = hTmpHandle;
					}
					/* clear the existing listboxes */
					SendMessage (GET_WM_COMMAND_HWND(wParam, lParam), WM_SETREDRAW, FALSE, 0L);

					hDlgSubList = GetDlgItem (hDlg, IDD_SUBSCRIBED_GROUP_LISTBOX);
					hDlgUnSubList = GetDlgItem (hDlg, IDD_UNSUBSCRIBED_GROUP_LISTBOX);
					SendMessage (hDlgUnSubList, LB_RESETCONTENT, 0, 0);
					SendMessage (hDlgSubList, LB_RESETCONTENT, 0, 0);

					hListboxDC = GetDC(hDlgSubList);
					/* load the subscribed/unsubscribed boxes */
					for (j = CurHier->Start; j < (CurHier->Start + CurHier->NumGroups); j++) 
					{
						TypGroup huge *group;
						group = (TypGroup huge *)
						((char huge *) NewGroupTable[j] + sizeof (TypLine));
						cptr = (char huge *) group + sizeof (TypGroup);
#ifdef _WIN32
						GetTextExtentPoint32(hListboxDC, cptr, strlen(cptr), &size);
						ext = (int)size.cx;
#else
						size = GetTextExtent(hListboxDC, cptr, strlen(cptr));
						ext = (int)LOWORD(size);
#endif

							if (group->Subscribed) 
							{
								SendMessage (hDlgSubList, LB_ADDSTRING, 0, (LPARAM)cptr);
								if (ext > maxSubExt) 
								{ 
									maxSubExt = ext;
								}
							} 
							else 
							{
								SendMessage (hDlgUnSubList, LB_ADDSTRING, 0, (LPARAM)cptr);
								if (ext > maxUnsubExt) 
								{ 
									maxUnsubExt = ext;
								}
							}     
						}
						ReleaseDC(hDlgSubList, hListboxDC);
    
						SendMessage(hDlgUnSubList,
									LB_SETHORIZONTALEXTENT,(WPARAM)maxUnsubExt,(LPARAM)0);
						SendMessage(hDlgSubList,
									LB_SETHORIZONTALEXTENT,(WPARAM)maxSubExt,(LPARAM)0);
  
						SendMessage (GET_WM_COMMAND_HWND(wParam, lParam), WM_SETREDRAW, TRUE, 0L);

						GlobalUnlock (hCurHier);
					break;
					default:
						return FALSE;
				}
				break;
			default:
				return FALSE;
		}
		break;

		case WM_DESTROY:
			/* Ok, now we tear down the hierarchy structure */
			hCurHier = hStartHier;
			while (hCurHier) 
			{
				HANDLE hTmpHandle;
				CurHier = (TypHier far *) GlobalLock (hCurHier);
				GlobalFree (CurHier->hName);
				hTmpHandle = CurHier->hNext;
				GlobalUnlock (hCurHier);
				GlobalFree (hCurHier);
				hCurHier = hTmpHandle;
			}
			break;

		default:
			return FALSE;
			break;
	}
	return TRUE;
}

/*--- function PositionEndSubscribed ----------------------------------
 *
 *  Position a pointer to the end of the subscribed section at the
 *  beginning of the net document.
 *
 *  Entry   None
 *
 *  Exit    BlockPtr and LinePtr  point to the place in NetDoc just
 *            beyond the last subscribed group.  We assume that
 *            all subscribed groups go at the beginning of the
 *            document.
 */
void PositionEndSubscribed (TypBlock far ** BlockPtr, TypLine far ** LinePtr)
{
	BOOL advance;
	TypGroup far *group;

	if (TopOfDoc (&NetDoc, BlockPtr, LinePtr)) 
	{
		advance = TRUE;
		do 
		{
			group = GetGroup(*LinePtr);
			if (group->Subscribed) 
			{
				advance = NextLine (BlockPtr, LinePtr);
			}
			else 
			{
				advance = FALSE;
			}
		}
		while (advance);
	}
}

/*--- function PositionNextSelected ----------------------------------
 *
 *  Position a pointer to the next selected group of the net document.
 *
 *  Entry   None
 *
 *  Exit    BlockPtr and LinePtr point to the place in NetDoc at
 *          the next selected group.  
 */
TypLineID PositionNextSelected (TypBlock far ** BlockPtr, TypLine far ** LinePtr)
{
	BOOL advance;
	TypGroup far *group;
	TypLineID nID = (*LinePtr)->LineID;

	advance = TRUE;
	do 
	{
		group = GetGroup(*LinePtr);
		if (group->Selected) 
		{
			advance = FALSE; 
		}
		else 
		{
			nID = (*LinePtr)->LineID;
			advance = NextLine (BlockPtr, LinePtr);
		}
	}
	while (advance);
	return nID;
}
 /*--- function PositionFirstSelected ----------------------------------
 *
 *  Position a pointer to the first selected group of the net document.
 *
 *  Entry   None
 *
 *  Exit    BlockPtr and LinePtr  point to the place in NetDoc at
 *          the first selected group.  
 */
TypLineID PositionFirstSelected (TypBlock far ** BlockPtr, TypLine far ** LinePtr)
{
	TypLineID nID =0;
	if (TopOfDoc (&NetDoc, BlockPtr, LinePtr)) 
	{
		nID = (*LinePtr)->LineID;
		nID = PositionNextSelected(BlockPtr,LinePtr);
	}
	return nID;
}

BOOL PositionAfterID (TypBlock far ** BlockPtr, TypLine far ** LinePtr, TypLineID nID )
{
	if (TopOfDoc (&NetDoc, BlockPtr, LinePtr)) 
	{
		int advance = 1;
		while (advance)
		{
			if (nID == (*LinePtr)->LineID)
			{
				NextLine(BlockPtr, LinePtr);
				return TRUE;
			}

			advance = NextLine (BlockPtr, LinePtr);
		}
	}
	TopOfDoc (&NetDoc, BlockPtr, LinePtr);
	return FALSE;
}

int CompareLineIDPos ( TypLineID nID1, TypLineID nID2 )
{
	
    TypBlock far *BlockPtr;
    TypLine far *LinePtr;
	
	if (nID1 == nID2)
		return 0;
	if (TopOfDoc (&NetDoc, &BlockPtr, &LinePtr)) 
	{
		int advance = 1;
		while (advance)
		{
			if (nID1 == (LinePtr)->LineID)
			{
				return -1;
			}
			else if (nID2 == (LinePtr)->LineID)
			{
				return 1;
			}
			advance = NextLine (&BlockPtr, &LinePtr);
		}
	}
	return 0;
}




/*--- function MergeGroups ----------------------------------------
 *
 *  Merge a list of groups into NetDoc.
 *
 *  Entry:  NewGroupTable  is an array of pointers to TypGroup structures
 *                         of groups to be merged into NetDoc.
 *          hNewGroupTable is the handle to the above.
 *          nNewGroups     is the number of groups in the table.
 *          WhereSubscribed   indicates where new subscribed groups
 *                         should be added.
 *                         ADD_SUBSCRIBED_END_OF_SUB indicates that
 *                           they should be added at the end of the subscribed
 *                           list, before the unsubscribed groups.
 *                         ADD_SUBSCRIBED_TOP_OF_DOC indicates that they
 *                           should be added at the top of the document.
 *  Exit:   The groups in the table have been added to NetDoc, and
 *            the entries in GroupTable have been freed from memory.
 *            Also, GroupTable itself has been freed.
 */
void MergeGroups (int WhereSubscribed, TypLineID TargetLineID)
{
	TypBlock far *BlockPtr;
	TypLine far *LinePtr;
	TypGroup far *group;
	char far *netcptr;
	char far *grpcptr;
	void far *AllocPtr;
	HANDLE hLine;
	unsigned int Offset;
	TypLineID MyLineID;
	char myline[BLOCK_SIZE];
	unsigned long j;
	int advance, at_end = 0;
	int percent,opercent;

	WVSetStatbarText(NetDoc.hWndFrame, IDS_STBAR_SORT_LIST,&NetDoc, TRUE, TRUE);

	switch (WhereSubscribed) 
	{
		case ADD_SUBSCRIBED_END_OF_SUB:
			PositionEndSubscribed (&BlockPtr, &LinePtr);
			break;
		case ADD_FIRST_SELECTED:
			PositionAfterID (&BlockPtr, &LinePtr, TargetLineID);
			break;
		case ADD_SUBSCRIBED_TOP_OF_DOC:
		default:
			TopOfDoc (&NetDoc, &BlockPtr, &LinePtr);
			break;
	}

	/* BlockPtr and LinePtr point to the
	* place to add new subscribed groups.
	* Loop through the new groups; for subscribed groups, add
	* them to NetDoc at this point.
	* For each subscribed group, unlock and free the corresponding
	* line pointed to by NewGroupTable.  Set the table entry
	* to 0 to indicate that this group has been dealt with.
	*/

	WVSetStatbarText(NetDoc.hWndFrame,IDS_STBAR_MERGE_GROUP,&NetDoc, TRUE, TRUE);
	SetStatbarPercent(NetDoc.hWndFrame,(percent = 0),&NetDoc,TRUE);
	opercent = percent;

	for (j = 0; j < nNewGroups; j++) 
	{  
		percent = (int) (((float) (j + 1) / (float) nNewGroups) * 100);
		if (percent > (opercent + 5)) 
		{  
			SetStatbarPercent(NetDoc.hWndFrame,percent,&NetDoc,TRUE);
			opercent = percent;
		} 

		AllocPtr = (char far *) NewGroupTable[j];
		group = GetGroup(AllocPtr);
		if (group->Subscribed) 
		{
			/* This group has been selected and should be subscribed to.*/
			MoveBytes (AllocPtr, myline, ((TypLine far *) AllocPtr)->length);
			LinePtr->active = TRUE;
			if (WhereSubscribed == ADD_FIRST_SELECTED) 
			{
				//DeleteLine (&BlockPtr, &LinePtr);
				AddLine ((TypLine *) myline, &BlockPtr, &LinePtr);
				//PositionNextSelected (&BlockPtr, &LinePtr);
				NetDoc.ActiveLines++;
			}
			else 
			{
				AddLine ((TypLine *) myline, &BlockPtr, &LinePtr);
				NetDoc.ActiveLines++;
			}
			NewGroupTable[j] = (void far *) 0;
		}
	}
	PositionEndSubscribed (&BlockPtr, &LinePtr);

	/* Now take a second pass through NewGroupTable, for the
	* unsubscribed groups.  If NewGroupTable[j] is non-zero, then
	* that group should be entered into the second, unsubscribed
	* section of NetDoc, merged in in alphabetical order.
	*
	* BlockPtr and LinePtr point to the first unsubscribed group.
	*/

	WVSetStatbarText(NetDoc.hWndFrame,IDS_STBAR_MERGE_UNSUBSCRIBED,&NetDoc, TRUE, TRUE);
	SetStatbarPercent(NetDoc.hWndFrame,(percent = 0),&NetDoc,TRUE);
	opercent = percent;

	for (j = 0; j < nNewGroups; j++) 
	{

		percent = (int) (((float) (j + 1) / (float) nNewGroups) * 100);
		if (percent > opercent + 5) 
		{  
			SetStatbarPercent(NetDoc.hWndFrame,percent,&NetDoc,TRUE);
			opercent = percent;
		} 
		if (NewGroupTable[j]) 
		{
			/* Search for the right place to add this line. */

				AllocPtr = (char far *) NewGroupTable[j];
				grpcptr = GetGroupName(AllocPtr);
				advance = TRUE;

			if (!at_end) 
			{
				do 
				{	//LinePtr already at END_BLOCK on entry so lstrcmp uses garbage
					//so check length too - MDS 2008/04/18
					netcptr = GetGroupName(LinePtr);
					
					if (LinePtr->length > 0 && lstrcmp (grpcptr, netcptr) < 0) 
					{
						advance = FALSE;
					}
					else 
					{
						advance = NextLine (&BlockPtr, &LinePtr);
						if (!advance)
						{
							at_end = 1;   /* possible bug, getting bad netcptr (smr) */
						}
					
					}
				}
				while (advance);
			}

			/* Now add the new group at this point */
			MoveBytes (AllocPtr, myline, ((TypLine far *) AllocPtr)->length);
			LinePtr->active = ShowUnsubscribed;
			/*df 2000-04-28 - Avoid uninit read */
			((TypLine *) myline)->active = FALSE;

			AddLine ((TypLine *) myline, &BlockPtr, &LinePtr);
		}
	}

	UnlockLine (BlockPtr, LinePtr, &hLine, &Offset, &MyLineID);
	SetStatbarPercent(NetDoc.hWndFrame,0,&NetDoc,TRUE);
	SetStatbarText(NetDoc.hWndFrame,"",&NetDoc, TRUE, TRUE);

}

/*--- function CleanUpGroupTable ------------------------------------
 *
 *  Clean up after doing processing to add or move groups in NetDoc.
 */
void CleanUpGroupTable ()
{
	HANDLE hBlock, hBlockNext;
	TypBlock far *BlockPtr;

	/* Unlock all blocks in the NetDoc document.
	*/

	hBlock = NetDoc.hFirstBlock;
	do 
	{
		BlockPtr = (TypBlock far *) GlobalLock (hBlock);
		hBlockNext = BlockPtr->hNextBlock;

		MyGlobalUnlock (hBlock);
		MyGlobalUnlock (hBlock);
		hBlock = hBlockNext;
	}
	while (hBlock);

	/* Unlock and free the NewGroupTable and NewGroupData.  */
	if (hNewGroupData)
	{
		GlobalUnlock (hNewGroupData);
		GlobalFree (hNewGroupData);
		hNewGroupData = NULL;
	}
	if (hNewGroupTable) 
	{
		MyGlobalUnlock (hNewGroupTable);
		GlobalFree (hNewGroupTable);
		hNewGroupTable = NULL;
	}
}

/*--- function FindNextPrime ------------------------------------
 *
 *  Find the smallest prime that is larger than the given argument.
 *  Extreemly handy for those annoying dynamic hash tables.
 */
unsigned long FindNextPrime (unsigned long OrigVal)
{

	int MaxPrimeIndex = (sizeof (prime) / sizeof (prime[0])) - 1;
	int IndexA = 0;
	int IndexB = MaxPrimeIndex;
	int TmpIndex;

	/* If we're larger than the max prime we know of, just return it */
	if (OrigVal >= prime[MaxPrimeIndex])
		return (prime[MaxPrimeIndex]);

	/* We do this by doing a binary search of the prime array */
	do 
	{
		TmpIndex = IndexA + ((IndexB - IndexA) / 2);
		if (OrigVal == prime[TmpIndex])
			return (prime[TmpIndex + 1]);
		if (OrigVal < prime[TmpIndex])
			IndexB = TmpIndex;
		else
			IndexA = TmpIndex;
	} while ((IndexB - IndexA) > 1);

	return (prime[IndexA + 1]);
}


/*--- function ProcListLine ---------------------------------------------
 *
 *  Process a line from a modified newsrc.
 */
#if 0
void ProcNewsrcLine (TypLine *LinePtrNew)
{
	unsigned long hashval;
	char far *textptr;
	unsigned char *cptr, *restline;
	char mygroupline[BLOCK_SIZE];
	TypGroup *mygroup;
	TypGroup far *netgroup;
	unsigned long ArtNum;
	int PercentDone;

	if ((++RcvLineCount) % UPDATE_TITLE_FREQ == 0) 
	{
		InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
		if (NetDoc.TotalLines > 0) 
		{
			//  PercentDone = MulDiv (RcvLineCount, 100, NetDoc.TotalLines);
			PercentDone = (int) ((RcvLineCount * 100) / NetDoc.TotalLines);
			if (PercentDone <= 100)
				SetStatbarPercent (NetDoc.hWndFrame, PercentDone, &NetDoc, TRUE);
		}
		if (RcvLineCount % (UPDATE_TITLE_FREQ * 25) == 0) 
		{
			UpdateWindow (NetDoc.hDocWnd);
		}
	}

	/* Replace the first blank in the input line with a zero. */
	for (cptr = ListLine; *cptr && *cptr != ' '; cptr++);
		*cptr = '\0';
	restline = cptr + 1;      /* points to highest art # */

	//MDS toss out long groups that we can't handle 19991210
	if (strlen( (char*) ListLine) >= MAXGROUPNAME)
		return;

	hashval = HashGroup (ListLine);
checkhash:;
	if (!NetHashTable[hashval]) 
	{
		/* This is a new group.
		* Create a Group line from the information in this line.
		*/

		ListLine += BuildGroup( (char*)ListLine, (TypLine *) (mygroupline));
		mygroup = (TypGroup *) (mygroupline + sizeof (TypLine));
		GetNum ((char**)&restline, &(mygroup->ServerLast));
		GetNum ((char **)&restline, &(mygroup->ServerFirst));
		mygroup->ServerEstNum = mygroup->ServerLast - mygroup->ServerFirst + 1;
		//mygroup->HighestPrevSeen = 0; // redundant
		mygroup->Determined = TRUE;
		//mygroup->NumUnread = 0L; // redundant
		AddGroupToTable (mygroupline);
	}
	else 
	{
		textptr = ((char far *) NetHashTable[hashval] + sizeof (TypLine) + sizeof (TypGroup));
		if (lstrcmp (textptr, (char*)ListLine)) 
		{
			hashval = (hashval + 1) % HashTableSize;
			goto checkhash;
		}
		else 
		{

			/* This group is already present in NetDoc.
			   Update the ServerFirst and ServerLast fields.*/
			netgroup = (TypGroup far *) ((char far *) NetHashTable[hashval] + sizeof (TypLine));
			GetNum ((char **)&restline, (unsigned long*) &ArtNum);
			netgroup->ServerLast = ArtNum;
			GetNum ((char **)&restline, &ArtNum);
			netgroup->ServerFirst = ArtNum;
			netgroup->ServerEstNum = (netgroup->ServerLast - netgroup->ServerFirst + 1);
			netgroup->Determined = TRUE;
			netgroup->NumUnread = CalcNumUnread(netgroup);
		}
	}
}

#endif

//
// Local Variables:
// tab-width: 2
// end:
//
