#pragma warning( disable : 4786 ) // Turn off map template length warning

#include <windows.h>
#include <windowsx.h>           // for GlobalFreePtr (JSC)
#include "wvglob.h"
#include "wvclass.h"
#include "winvn.h"
#include "wvnzb.h"
#pragma hdrstop


#include <malloc.h>
#include <set>
#include <map>

extern bool ReadStr( HANDLE hFile, char * lpsz, unsigned long nMax, unsigned long *pdwActual );

//count the number of occurences of a char in a string
int strcount(const char * szStr, const char c)
{
	int nRet = 0;
	while (szStr && *szStr)
		if (*(szStr++) == c)
			nRet++;
	return nRet;
}

//escape any reserved XML characters
// allocated string memory needs to be freed
char * XMLEncode(const char * szLine)
{
	static char cZot = 0;
	char *pTemp = NULL;

	if (!szLine )
		return NULL;


	size_t nExtra = strlen(szLine) +1;
	nExtra += (strcount(szLine, '"') *6);
	nExtra += (strcount(szLine, '\'') *6);
	nExtra += (strcount(szLine, '&') *5);
	nExtra += (strcount(szLine, '<') *4);
	nExtra += (strcount(szLine, '>') *4);
	
	pTemp = (char *) malloc(nExtra);
	if (!pTemp)
		return NULL;

	char *p = pTemp;
	while (*szLine )
	{
		switch (*szLine )
		{
			case '"':
				strcpy(p, "&quot;");
				p+=5;
				break;
			case '\'':
				strcpy(p, "&apos;"); //use   &#39; or &#x0027;  not &apos;
				p+=5;
				break;
				
			case '&':
				strcpy(p, "&amp;"); //use   &#38; or &#x0026;  not &amp;
				p+=4;
				break;

			case '<':
				strcpy(p, "&lt;");
				p+=3;
				break;
			case '>':
				strcpy(p, "&gt;");
				p+=3;
				break;
			default:
				*p = *szLine;
			break;
		}
		p++;
		szLine++;

	}
	*p = '\0';

	return pTemp;

}

// convert ansi to UTF-8
// allocated string memory needs to be freed
char * UTF8Encode( const char * szIn)
{
	char * pOut = NULL;
	WCHAR *pwStr = NULL;

	//ansi->unicode
	int nLen = MultiByteToWideChar(CP_ACP, 0, szIn, -1, NULL, NULL);
	
	pwStr = new WCHAR [nLen+1];
	MultiByteToWideChar(CP_ACP, 0, szIn, -1, pwStr, nLen);

	
	nLen = WideCharToMultiByte(CP_UTF8, 0, pwStr, -1, NULL, 0, NULL, NULL);
	pOut = (char*) malloc (nLen+1);
	//now out to UTF-8
	WideCharToMultiByte(CP_UTF8, 0, pwStr, -1, pOut, nLen+1, NULL, NULL);

	if (pwStr)
		delete pwStr;
	

	return pOut;	 


}

/*
void SwapHeaderEntryData(HEADERENTRY* pEntry1, HEADERENTRY* pEntry2)
{
	int	nPart	= pEntry1->nPart;
	int nTotal	= pEntry1->nTotal;
	int nGPart	= pEntry1->nGPart;
	int nGTotal = pEntry1->nGTotal;
	unsigned long nIndex = pEntry1->nIndex;
	char * Subject = pEntry1->Subject;
	header_p HeaderP = pEntry1->HeaderP;
	struct tagHEADERENTRY *pDupes = pEntry1->pDupes;
	
	pEntry1->nPart  =	pEntry2->nPart;  
	pEntry1->nTotal	=	pEntry2->nTotal;	
	pEntry1->nGPart	=	pEntry2->nGPart;	
	pEntry1->nGTotal =	pEntry2->nGTotal;
	pEntry1->nIndex	 =	pEntry2->nIndex;	
	pEntry1->Subject =	pEntry2->Subject;
	pEntry1->HeaderP =	pEntry2->HeaderP;
	pEntry1->pDupes	 =	pEntry2->pDupes;
	
	pEntry2->nPart = nPart; 
	pEntry2->nTotal	= nTotal;
	pEntry2->nGPart	= nGPart;
	pEntry2->nGTotal = nGTotal;
	pEntry2->nIndex	= nIndex;
	pEntry2->Subject = Subject;
	pEntry2->HeaderP = HeaderP;
	pEntry2->pDupes	= pDupes;
	
} 
*/
//find first occurrence of s2 in s1 of length lens1
const char * strnstr(const char far *s1, const char far *s2, int lens1)
{
    int n, m, l, o, bfound;
    const char *c1 = NULL;
    
    n=m=l=bfound=0;
    
    while ( *(s1+n) && n < lens1)
        n++;
    
    while ( *(s2+m))
        m++;
    
    if (m > n) 
    return 0;
    
    m--;
    while ( l < n - m && !bfound)
    {
        if (UPC(*(s1+l)) == UPC(*(s2)))
        {
            c1 = (const char *) s1+l;
            for (o = 0; o <= m; o++)
            {
                if ( UPC(*(c1+o)) != UPC(*(s2+o)))
                {
                    break;
                }
                if ( o == m)
                    bfound = 1;
            }
        }
        if (!bfound)
            l++;    
    }
    if (bfound)
        return (char *) c1;
    else
        return 0;
     
}

//find first occurrence of s2  of length lens2 in s1
const char * strstrn(const char far *s1, const char far *s2, int lens2)
{
    int n, m, l, o, bfound;
    const char *c1 = NULL;
    
    n=m=l=bfound=0;
    
    while ( *(s1+n) )
        n++;
    
    while ( *(s2+m) && m < lens2)
        m++;
    
    if (m > n) 	  //if search len > string len, exit
		return 0;
    
    m--;
    while ( l < n - m && !bfound)
    {
        if (UPC(*(s1+l)) == UPC(*(s2)))
        {
            c1 = (const char *) s1+l;
            for (o = 0; o <= m; o++)
            {
                if ( UPC(*(c1+o)) != UPC(*(s2+o)))
                {
                    break;
                }
                if ( o == m)
                    bfound = 1;
            }
        }
        if (!bfound)
            l++;    
    }
    if (bfound)
        return (char *) c1;
    else
        return 0;
     
}

WVFileSortHeaders::WVFileSortHeaders()
{
}
WVFileSortHeaders::~WVFileSortHeaders()
{
	ClearList();

}

void WVFileSortHeaders::ClearList()
{

	//free memory
	for (m_sitr = m_SubjList.begin(); m_sitr != m_SubjList.end(); m_sitr++)
	{
		LPHEADERENTRY pNext, pEntry = *m_sitr;
		
		pNext = pEntry->pDupes;
		while (pNext)
		{
			LPHEADERENTRY pCur = pNext;
			pNext = pCur->pDupes;
			free (pCur);	
		}
		pNext = pEntry;
		free (pEntry);
		pNext = NULL;

	}
	
	m_FilenameList.clear();
	m_SubjList.clear();

}

LPHEADERENTRY WVFileSortHeaders::ProcessSubject(LPCSTR szSubject, header_p pheader)
{
   size_t nMaxString = strlen(szSubject) +2;
   
   //figure out space for this header
   int nSize = sizeof(HEADERENTRY) + nMaxString;

   //create a new Entry
   HEADERENTRY* pEntry = (HEADERENTRY*) malloc( nSize);
   if (pEntry)
   {
		pEntry->pDupes = NULL;
		pEntry->pFiles = NULL;
		pEntry->HeaderP = pheader;
		
		pEntry->Subject = (char *) (pEntry+1);
		*pEntry->Subject = '\0';
		pEntry->Filename = pEntry->Subject;
		pEntry->nPart = -1;
		pEntry->nTotal = -1;
		pEntry->nGPart = -1;
		pEntry->nGTotal = -1;
		pEntry->nFNameLen = 0;
		pEntry->nIndex = (unsigned long) -1;
		int nLen1;
		const char *p;

		//Fill in the data
		p = ParseSubject(szSubject, &nLen1, &pEntry->nPart, &pEntry->nTotal, &pEntry->nGPart, &pEntry->nGTotal, pEntry->Subject, nMaxString );			

		if (!*pEntry->Subject)
		{
			if (p && *p)
			{
				strncpy(pEntry->Subject, p, nLen1);
				pEntry->Subject[nLen1] = 0;
			}
			else
				strcpy(pEntry->Subject, szSubject);
		}
		else if (p && nLen1)
		{
			char * tp = (char *) strstrn(pEntry->Subject, p, nLen1);
			if (tp)
			{
				pEntry->Filename = tp; 
				pEntry->nFNameLen = nLen1;
			}

		}
		if  (pEntry->nPart > pEntry->nTotal && pEntry->nTotal > 0)
		{
			//85 of 32? 
			pEntry->nPart = -1;
			pEntry->nTotal = -1;
		}
	}

	return pEntry;


}

long WVFileSortHeaders::SortDoc(TypDoc far * Doc, unsigned char selFlag/* =ART_SELECTED*/)
{
	TypGroup far *group;
	HANDLE header_handle, thread_handle;
    header_p headers, ThisHeader;
    TypBlock far *BlockPtr;
    TypLine far *LinePtr;
    HANDLE hBlock;
    unsigned int Offset;
    TypLineID MyLineID;

	unsigned long index; 
	long num_affected = 0;
	BOOL bOK = TRUE; 

	


	if (!Doc)
		return -1;

	//Create sorted list of unique subjects
    if (LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID, &BlockPtr, &LinePtr))
	{
		group = GetGroup(LinePtr);
		header_handle = group->header_handle;
		thread_handle = group->thread_handle;

		if (header_handle) 
		{
			headers = lock_headers (header_handle, thread_handle);

			for (index = 0 ; bOK && index < group->total_headers && headers; index++) 
			{
				ThisHeader = header_elt (headers, index);

				if( !selFlag || (selFlag & ThisHeader->SeenSelected))
				{
				   HEADERENTRY* pEntry = WVFileSortHeaders::ProcessSubject(ThisHeader->pHD->sSubject, ThisHeader);
				   if (pEntry)
				   {
						pEntry->nIndex = index;
						num_affected++;

						m_sitr = m_SubjList.find(pEntry);
						if (m_sitr != m_SubjList.end())
						{
							//duplicate	entry, insert in list of parts
							LPHEADERENTRY pNext, pCur = *m_sitr;
							pNext = pCur->pDupes;

							//this part is < existing 1st part, replace 1st part
							if (pCur->nPart > pEntry->nPart && pCur->nTotal >= pEntry->nTotal)
							{
								//append existing entries to new entry
								pEntry->pDupes = pCur;
								//replace list entry with new entry
								//SwapHeaderEntryData( *m_sitr, pEntry);
								((HEADERENTRY*)(*m_sitr)) = pEntry;
							}
							else
							{
								//find position to insert this part in list
								//1/1
								//1/2
								//2/2
								//1/3
								//2/3
								//3/3

								while (pNext &&  ((pCur->nTotal < pEntry->nTotal) || 
										( pEntry->nTotal ==  pCur->nTotal && pCur->nPart <= pEntry->nPart )) )
								{
									if ( pEntry->nTotal ==  pCur->nTotal && pCur->nPart == pEntry->nPart &&
									     pEntry->nTotal == -1 && pEntry->nPart == -1)
									{
										//just stop looking
										break;
									}
									
									if ( (pNext->nTotal == pEntry->nTotal && pNext->nPart > pEntry->nPart) || 
										  (pNext->nTotal > pEntry->nTotal) )
										break; //duplicate part goes after here before next one
									
									
									pCur = pNext;
									pNext = pCur->pDupes;
									if (pNext == pCur)
									{
										//paranoia
										TRACE("***Error in sort list***\r\n");
										pNext->pDupes = NULL;
										pNext = NULL;
									}
								}
								pCur->pDupes = pEntry;
								if (pNext)
									pEntry->pDupes = pNext;
							}



						}
						else
						{
							//not found, create a new entry
							m_SubjList.insert(pEntry);

						}

						//make a list of filenames
						m_fitr = m_FilenameList.find(pEntry);
						if (m_fitr != m_FilenameList.end())
						{
							//duplicate	entry, insert in list of parts
							LPHEADERENTRY pNext, pCur = *m_fitr;
							pNext = pCur->pFiles;

							//this part is < existing 1st part, replace 1st part
							if (pCur->nPart > pEntry->nPart && pCur->nTotal >= pEntry->nTotal)
							{
								//append existing entries to new entry
								pEntry->pFiles = pCur;
								//replace list entry with new entry
								//SwapHeaderEntryData( *m_fitr, pEntry);
								((HEADERENTRY*)(*m_fitr)) = pEntry;
							}
							else
							{

								while (pNext &&  ((pCur->nTotal < pEntry->nTotal) || 
										( pEntry->nTotal ==  pCur->nTotal && pCur->nPart <= pEntry->nPart)) )
								{
									
									if ( pEntry->nTotal ==  pCur->nTotal && pCur->nPart == pEntry->nPart &&
									     pEntry->nTotal == -1 && pEntry->nPart == -1)
									{
										//just stop looking
										break;
									}									
									
									if ( (pNext->nTotal == pEntry->nTotal && pNext->nPart > pEntry->nPart) || 
										  (pNext->nTotal > pEntry->nTotal) )
										break; //duplicate part goes after here before next one
									
									
									pCur = pNext;
									pNext = pCur->pFiles;
									if (pNext == pCur)
									{
										//paranoia
										TRACE("***Error in sort list***\r\n");
										pNext->pFiles = NULL;
										pNext = NULL;
									}
								}
								pCur->pFiles = pEntry;
								if (pNext)
									pEntry->pFiles = pNext;
							}



						}
						else
						{
							m_FilenameList.insert(pEntry);

						}
				   
				   }

				}

			}
			unlock_headers (header_handle, thread_handle);
		}
		
		UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
	}

	return bOK ? num_affected : -num_affected;


}

/*LPHEADERENTRY WVFileSortHeaders::FindBySubject( LPCSTR szSubject )
{

	
	
	return NULL;

}
LPHEADERENTRY WVFileSortHeaders::FindByFilename( LPCSTR szSubject )
{

	return NULL;

}*/

LPHEADERENTRY WVFileSortHeaders::FindHeaderSubject( LPHEADERENTRY pHeader )
{
	if (!pHeader) return NULL;

	m_sitr = m_SubjList.find(pHeader);
	if ( m_sitr == m_SubjList.end())
		return NULL;
	return *m_sitr;


}
LPHEADERENTRY WVFileSortHeaders::FindHeaderFilename( LPHEADERENTRY pHeader )
{
	if (!pHeader) return NULL;

	m_fitr = m_FilenameList.find(pHeader);
	if ( m_fitr == m_FilenameList.end())
		return NULL;
	return *m_fitr;

}
int WVFileSortHeaders::SelectArticlesBySubject( LPHEADERENTRY pEntry, int &nTotal, char nFlag, BOOL bIgnoreThisEntry)
{

	if (!pEntry)
		return 0;

	LPHEADERENTRY pListGroup = FindHeaderSubject(pEntry);
	if(	pListGroup)
	{
		int nLastEntry = -1;
		int nAffected = 1;
		nTotal = -1;
		BOOL bSetFlag = nFlag > 0;
		nFlag = (char) abs(nFlag);
		while (	pListGroup)
		{
											
			if ( (nLastEntry < 0 || (pListGroup->nPart >= 0 && nLastEntry != pListGroup->nPart) ) &&
				(bIgnoreThisEntry ? (pListGroup->nPart != pEntry->nPart) : !(pListGroup->nPart == pEntry->nPart && pEntry->HeaderP != pListGroup->HeaderP) ) &&
				pEntry->nTotal == pListGroup->nTotal  )
			{
				nLastEntry = pListGroup->nPart;
				if ( bIgnoreThisEntry ? (pEntry->HeaderP != pListGroup->HeaderP)  : 1)
				{
					if (bSetFlag)
						pListGroup->HeaderP->SeenSelected |= nFlag;
					else
						pListGroup->HeaderP->SeenSelected &= ~nFlag;
					nAffected++;
				}

			}
			if (nTotal < pEntry->nTotal)
				nTotal = pEntry->nTotal;

		
			pListGroup = pListGroup->pDupes;

		}

		return nAffected;
	}

	return 0;



}


void ShowList(HEADERENTRY* pEntry)
{
	int i = 0;

	HEADERENTRY* pList = pEntry;
	TRACE("Subj List\r\n");
	while (pList && i < 100)
	{
		char szBuf[800] = {0};
		strncpy(szBuf, pList->Filename, pList->nFNameLen);
		
		TRACE("Entry #%d, Part=%d, Total=%d, Subj=%s,File=%s \r\n", i, pList->nPart, pList->nTotal, pList->Subject, szBuf);
		i++;

		pList = pList->pDupes;
	}
	TRACE("File List\r\n");
	pList = pEntry;
	i= 0;
	while (pList && i < 100)
	{
		char szBuf[800] = {0};
		strncpy(szBuf, pList->Filename, pList->nFNameLen);
		
		TRACE("Entry #%d, Part=%d, Total=%d, Subj=%s,File=%s \r\n", i, pList->nPart, pList->nTotal, pList->Subject, szBuf);
		i++;

		pList = pList->pFiles;;
	}
	TRACE("\r\n");

}



long FileSortHeaders(TypDoc far * Doc, HEADERLIST* pList, unsigned char selFlag /*=ART_SELECTED*/ )
{

    TypGroup far *group;
	HANDLE header_handle, thread_handle;
    header_p headers, ThisHeader;
    TypBlock *BlockPtr;
    TypLine  *LinePtr;
	long index, num_affected = 0;
	BOOL bOK = TRUE; 

    HANDLE hBlock;
    unsigned int Offset;
    TypLineID MyLineID;


	HEADERLIST::iterator itr;
	FHEADERLIST::iterator f_itr;
	FHEADERLIST fList;

	if (!Doc || !pList)
		return -1;

	//Create sorted list of unique subjects
    if (LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID, &BlockPtr, &LinePtr))
	{
		group = GetGroup(LinePtr);
		header_handle = group->header_handle;
		thread_handle = group->thread_handle;
		if (header_handle) 
		{
			headers = lock_headers (header_handle, thread_handle);

			for (index = 0 ; bOK && index < group->total_headers && headers; index++) 
			{
				ThisHeader = header_elt (headers, index);

				if( !selFlag || (selFlag & ThisHeader->SeenSelected))
				{
				   int nMaxString = strlen(ThisHeader->pHD->sSubject) +2;
				   num_affected++;
				   //figure out space for this header
				   int nSize = sizeof(HEADERENTRY) + strlen(ThisHeader->pHD->sSubject) +2;

				   //create a new Entry
				   HEADERENTRY* pEntry = (HEADERENTRY*) malloc( nSize);
				   if (pEntry)
				   {
						pEntry->pDupes = NULL;
						pEntry->pFiles = NULL;
						pEntry->HeaderP = ThisHeader;
						
						pEntry->Subject = (char *) (pEntry+1);
						*pEntry->Subject = '\0';
						pEntry->Filename = pEntry->Subject;
						pEntry->nPart = -1;
						pEntry->nTotal = -1;
						pEntry->nGPart = -1;
						pEntry->nGTotal = -1;
						pEntry->nFNameLen = 0;
						pEntry->nIndex = index;
						int nLen1;
						const char *p;
						//Fill in the data
						p = ParseSubject(ThisHeader->pHD->sSubject, &nLen1, &pEntry->nPart, &pEntry->nTotal, &pEntry->nGPart, &pEntry->nGTotal, pEntry->Subject, nMaxString );			

						if (!*pEntry->Subject)
						{
							if (p && *p)
							{
								strncpy(pEntry->Subject, p, nLen1);
								pEntry->Subject[nLen1] = 0;
							}
							else
								strcpy(pEntry->Subject, ThisHeader->pHD->sSubject);
						}
						else
						{
							char * tp = (char *) strstrn(pEntry->Subject, p, nLen1);
							if (tp)
							{
								pEntry->Filename = tp; 
								pEntry->nFNameLen = nLen1;
							}

						}
						
						itr = pList->find(pEntry);
						if (itr != pList->end())
						{
							

							
							//duplicate	entry, insert in list of parts
							LPHEADERENTRY pNext, pCur = *itr;
							pNext = pCur->pDupes;

							//this part is < existing 1st part, replace 1st part
							if (pCur->nPart > pEntry->nPart)
							{
								//append existing entries to new entry
								pEntry->pDupes = pCur;
								//replace list entry with new entry
								//SwapHeaderEntryData( *itr, pEntry);
								((HEADERENTRY*)(*itr)) = pEntry;
							}
							else
							{
								//find position to insert this part in list
								while (pNext && pCur->nPart <= pEntry->nPart)
								{
									if (pNext->nPart > pEntry->nPart)
										break; //duplicate part goes after here before next one
									pCur = pNext;
									pNext = pCur->pDupes;
									if (pNext == pCur)
									{
										//paranoia
										TRACE("***Error in sort list***\r\n");
										pNext->pDupes = NULL;
										pNext = NULL;
									}
								}
								pCur->pDupes = pEntry;
								if (pNext)
									pEntry->pDupes = pNext;
							}




						}
						else
						{
							//not found, create a new entry
							pList->insert(pEntry);
						}

						//make a list of filenames
						f_itr = fList.find(pEntry);
						if (f_itr != fList.end())
						{
							//duplicate	entry, insert in list of parts
							LPHEADERENTRY pNext, pCur = *f_itr;
							pNext = pCur->pFiles;

							//this part is < existing 1st part, replace 1st part
							if (pCur->nPart > pEntry->nPart)
							{
								//append existing entries to new entry
								pEntry->pFiles = pCur;
								//replace list entry with new entry
								//SwapHeaderEntryData( *f_itr, pEntry);
								((HEADERENTRY*)(*f_itr)) = pEntry;
							}
							else
							{
								//find position to insert this part in list
								while (pNext && pCur->nPart <= pEntry->nPart)
								{
									if (pNext->nPart > pEntry->nPart)
										break; //duplicate part goes after here before next one
									pCur = pNext;
									pNext = pCur->pFiles;
									if (pNext == pCur)
									{
										//paranoia
										TRACE("***Error in sort list***\r\n");
										pNext->pFiles = NULL;
										pNext = NULL;
									}
								}
								pCur->pFiles = pEntry;
								if (pNext)
									pEntry->pFiles = pNext;
							}

						}
						else
						{
							fList.insert(pEntry);
						}
				   
				   }

				}

			}
			unlock_headers (header_handle, thread_handle);
		}

		UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
	}

	return bOK ? num_affected : -num_affected;

}

void FreeFileSortHeaders(HEADERLIST* pList)
{
	
	HEADERLIST::iterator itr;
	if (!pList)
		return;
	//free memory
	for (itr = pList->begin(); itr != pList->end(); itr++)
	{
		LPHEADERENTRY pNext, pEntry = *itr;
		
		pNext = pEntry->pDupes;
		while (pNext)
		{
			LPHEADERENTRY pCur = pNext;
			pNext = pCur->pDupes;
			free (pCur);	
		}
		pNext = pEntry;
		free (pEntry);
		pNext = NULL;

	} 

}

BOOL IsFileNZB( const char * szFile)
{
	
	BOOL bRet = FALSE;
	char scratch_line[2001];
	HANDLE hFile = CreateFile(szFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, 
						OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile != INVALID_HANDLE_VALUE)
	{
		DWORD dwPos = GetFileSize(hFile, NULL);
		if (dwPos > 100)
		{
			DWORD dwRead = 0;
			//figure out encoding
			SetFilePointer(hFile, 0 , NULL, FILE_BEGIN);
			if (ReadFile(hFile, scratch_line, min(2000, dwPos), &dwRead, NULL))
			{
				scratch_line[dwRead] = 0;
				char *p = stristr(scratch_line, "<?xml");
				if (p)
				{
					if (stristr(p, "<nzb"))
						bRet = TRUE;
				}
			}
		}
		CloseHandle(hFile);

	}
	return bRet;

}

//Create a .nzb format dump of selected articles
//
// format is:
//
//(header)
//<file subject ="blah blah" date="1175058765" poster="someguy">
//<groups>
//<group>alt.some.group</group>
//</groups>
//<segments>
//<segment bytes="12345" number="1">some-message-id</segment>
//</segments>
//</file>
//... more files
//</nzb>

long SaveArtNZB( TypDoc far * Doc, HANDLE hFile, BOOL bSelected, BOOL bAppend /*= FALSE*/, BOOL bUTF8 /*=TRUE*/)
{

	const char szNZBHeader[] = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\r\n"
		"<!DOCTYPE nzb PUBLIC \"-//newzBin//DTD NZB 0.9//EN\" \"http://www.newzbin.com/DTD/nzb/nzb-0.9.dtd\">\r\n"
		"<nzb xmlns=\"http://www.newzbin.com/DTD/2003/nzb\">\r\n";
	const char szNZBHeaderUTF[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
		"<!DOCTYPE nzb PUBLIC \"-//newzBin//DTD NZB 0.9//EN\" \"http://www.newzbin.com/DTD/nzb/nzb-0.9.dtd\">\r\n"
		"<nzb xmlns=\"http://www.newzbin.com/DTD/2003/nzb\">\r\n";



    TypBlock  *BlockPtr;
    TypLine  *LinePtr;
    HANDLE hBlock;
    unsigned int Offset;
    TypLineID MyLineID;

	long num_affected = 0;
	char scratch_line[MAXCOMMLINE];
	DWORD dwWrote;
	BOOL bOK = TRUE;

	HEADERLIST theList;
	HEADERLIST::iterator itr;
	WVString sCurrentGroup;

	if (LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID, &BlockPtr, &LinePtr))
	{
		sCurrentGroup =  ((char far *)LinePtr + sizeof(TypLine) + sizeof(TypGroup));
		UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
	}

	//rewind file & find out if it's a nzb file
	if (bAppend)
	{
		bAppend = FALSE;
		DWORD dwPos = GetFileSize(hFile, NULL);
		if (dwPos > 10)
		{
			DWORD dwRead;
			//figure out encoding of existing file & override arg
			SetFilePointer(hFile, 0 , NULL, FILE_BEGIN);
			ReadFile(hFile, scratch_line, 100, &dwRead, NULL);

			scratch_line[100] = 0;
			if (stristr(scratch_line, "utf-8"))
				bUTF8 = TRUE;
			else
				bUTF8 = FALSE;
		
			SetFilePointer(hFile, dwPos -10 , NULL, FILE_BEGIN);
			ReadFile(hFile, scratch_line, 10, &dwRead, NULL);
			scratch_line[dwRead] = '\0';
			char *p = strstr(scratch_line, "</nzb>");
			if (p)
			{
				//overwrite closing tag to append more data
				dwPos = dwPos - 10 + p-scratch_line;
				SetFilePointer(hFile, dwPos , NULL, FILE_BEGIN);
				SetEndOfFile(hFile);
				bAppend = TRUE;
			}
								
		}
		 
	}
	
	if (!bAppend && SetFilePointer(hFile, 0, NULL, FILE_END) == 0)
	{
		if (bUTF8)
		{
			bOK = WriteFile(hFile, szNZBHeaderUTF, (DWORD) strlen(szNZBHeaderUTF), &dwWrote, NULL);
		}
		else
		{
			bOK = WriteFile(hFile, szNZBHeader, (DWORD) strlen(szNZBHeader), &dwWrote, NULL);
		}
	}

	num_affected =  FileSortHeaders(Doc, &theList, bSelected ? ART_SELECTED : 0 );

	
    if (num_affected) 
    {


		//Now dump the sorted list
		for (itr = theList.begin(); bOK && itr != theList.end(); itr++)
		{
			LPHEADERENTRY pNext, pEntry = *itr;
			//write 1st entry
			char *p1 = NULL;
			char *p2 = NULL;
			
			if (bUTF8)
			{
				char *u1 = UTF8Encode( pEntry->Subject);
				char *u2 = UTF8Encode(pEntry->HeaderP->pHD->sFrom);

				p1 = XMLEncode(u1);
				p2 = XMLEncode(u2);
				if (u1)
					free(u1);

				if (u2)
					free(u2);

			}
			else
			{
				p1 = XMLEncode( pEntry->Subject);
				p2 = XMLEncode(pEntry->HeaderP->pHD->sFrom);
			}
			wsprintf(scratch_line, "<file subject=\"%s (1/%d)\" date=\"%u\" poster=\"%s\">\r\n",
				p1,
				pEntry->nTotal > 0 ? pEntry->nTotal : 1,
				mktime (&pEntry->HeaderP->pHD->datetime),
				p2);

			if (p1)
				free (p1);
			if (p2)
				free (p2);
			

			const char *pEnd = pEntry->HeaderP->pHD->sNewsgroups + strlen(pEntry->HeaderP->pHD->sNewsgroups);
			const char *pStart = pEntry->HeaderP->pHD->sNewsgroups;
			
			if ( bOK)
			{
				bOK = WriteFile(hFile, scratch_line, (DWORD) strlen(scratch_line), &dwWrote, NULL); 
				wsprintf(scratch_line, "<groups>\r\n");
				if (bOK)
					bOK = WriteFile(hFile, scratch_line, (DWORD) strlen(scratch_line), &dwWrote, NULL);
			
				if (pStart < pEnd)
				{
					while (	pStart < pEnd && bOK)
					{
						strcpy(scratch_line, "<group>");
						const char *p  = strchr(pStart, ',');
						if (!p)
							p = pEnd+1;
						strncat(scratch_line, pStart, p-pStart);
						strcat(scratch_line, "</group>\r\n");
						bOK = WriteFile(hFile, scratch_line, (DWORD)strlen(scratch_line), &dwWrote, NULL);
						pStart = ++p;
					}
				}
				else
				{
					//we have no group, maybe because xhdr didn't load it... well, we know this group
					wsprintf(scratch_line,"<group>%s</group>\r\n", sCurrentGroup);
					bOK = WriteFile(hFile, scratch_line, (DWORD)strlen(scratch_line), &dwWrote, NULL);
				}
				
				if (bOK)
				{
					wsprintf(scratch_line,"</groups>\r\n<segments>\r\n");
					bOK = WriteFile(hFile, scratch_line, (DWORD)strlen(scratch_line), &dwWrote, NULL); 
				}
			}
			
			int i = 0;
			pNext = pEntry;
			while (pNext && bOK)
			{
				
				char *p1 = NULL;
				if (bUTF8)
				{
					char *u1 = UTF8Encode(pNext->HeaderP->pHD->sMessage_id);
					p1 = XMLEncode( u1);
					if (u1)
						free(u1);
				}
				else
				{
					p1 = XMLEncode( pNext->HeaderP->pHD->sMessage_id);
				}

				i++;
				int bytes = pNext->HeaderP->bytes;
				if (!bytes)
				{
					if (pNext->HeaderP->lines)
						bytes = pNext->HeaderP->lines * 125;// assume ~yenc these days
					else
						bytes = 12345; //anything	
				}

				//write subitems
				wsprintf(scratch_line, "<segment bytes=\"%d\" number=\"%d\">%s</segment>\r\n",
					bytes, 
					pNext->nPart >= 0 ? pNext->nPart : 1,
					p1); 
				bOK = WriteFile(hFile, scratch_line, (DWORD)strlen(scratch_line), &dwWrote, NULL); 
				
				if (p1)
					free(p1);

				pNext = pNext->pDupes;
				
			}
			if (bOK)
			{
				wsprintf(scratch_line, "</segments>\r\n</file>\r\n");
				bOK = WriteFile(hFile, scratch_line, (DWORD)strlen(scratch_line), &dwWrote, NULL); 
			}

		}

		//closing tag
		if (bOK)
		{
			wsprintf(scratch_line, "</nzb>\r\n");
			bOK = WriteFile(hFile, scratch_line, (DWORD)strlen(scratch_line), &dwWrote, NULL); 
		}
		 
		//free memory
		FreeFileSortHeaders(&theList);


    }
	return bOK ? num_affected : -num_affected;


}

struct ltstr
{
  bool operator()(const char* s1, const char* s2) const
  {
    return strcmp(s1, s2) < 0;
  }
};


typedef std::map<const char*, BIGINT, ltstr> M_IDList;


long SelectLoadNZBArtList( TypDoc far * Doc, HANDLE hFile, long* pnTotal /*=NULL*/ )
{

    TypGroup far *group;
    HANDLE header_handle, thread_handle;
    header_p headers, ThisHeader;
    TypBlock *BlockPtr;
    TypLine *LinePtr;
    HANDLE hBlock;
    unsigned int Offset;
    TypLineID MyLineID;

    long index;
    long num_affected = 0;
	long nTotal = 0;
	char scratch_line[MAXINTERNALLINE];
	char mid_line[MAXINTERNALLINE];
	DWORD dwRead = 0;

	BIGINT *pList = NULL;

	int nCount = 0;

	//make a list of message IDs in the group;

	M_IDList MIdmap;
	M_IDList::iterator itr;

    if (LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID, &BlockPtr, &LinePtr))
	{
		group = GetGroup(LinePtr);
		header_handle = group->header_handle;
		thread_handle = group->thread_handle;

		if (group->total_headers < 1)
		{
			unlock_headers (header_handle, thread_handle);
			UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
			return 0;
		}

    
		if (header_handle) 
		{
			pList = new BIGINT[group->total_headers];

			headers = lock_headers (header_handle, thread_handle);

			for (index = 0 ;  index < group->total_headers && headers; index++) 
			{
				ThisHeader = header_elt (headers, index);

				if (ThisHeader->pHD)
				{
					MIdmap[ThisHeader->pHD->sMessage_id] = ThisHeader->number;	
				}
				
			}

			unlock_headers (header_handle, thread_handle);
		}
		UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
	}

	
	SetFilePointer(hFile, 0, NULL, FILE_BEGIN);

	//read selection
	while (ReadStr(hFile, scratch_line, MAXINTERNALLINE, &dwRead) )
	{
		char *p = strstr(scratch_line, "<segment");
		char *xml = mid_line;
		if (p && _strnicmp(p, "<segments>", 10) )
		{
			p = p+8;
			while (*p && *p != '>')
				p++;
			if (*p)
				p++;
			while (*p && *p != '<')
			{
				if (*p == '&')
				{
					if (!_strnicmp(p, "&quot;", 6))
					{
						*xml++ = '"';
						p+=5;
					}
					else if (!_strnicmp(p, "&apos;",6))
					{
						*xml++ = '\\';
						p+=5;
					}
					else if (!_strnicmp(p, "&amp;", 5))
					{
						*xml++ = '&';
						p+=4;
					}
					else if (!_strnicmp(p, "&lt;", 4))
					{
						*xml++ = '<';
						p +=3;
					}
					else if (!_strnicmp(p, "&gt;", 4))
					{
						*xml++ = '>';
						p += 3;
					}
					else if (*(p+1) == '#')
					{
						char *r = p+2;
						int val = 0;
						if (*r == 'x' || *r == 'X')
						{
							r++;
							while (isxdigit( (unsigned char) *r))
								val = val << 4 | dehex(*r++);
						}
						else
						{
							while (isdigit( (unsigned char) *r))
								val = val*10 +*r++ -'0';
						}
						if (*r == ';')
						{
							
							*xml++ = (char) (val ? val : ' ');
							p = r;

						}
						else
						{	//malformed, what to do?
							*xml++ = *p;
						}

					}
					else 
						*xml++ = *p;

				}
				else
				{
					*xml++ = *p;
				}

				p++;
			}
			*xml++ = '\0';
			nTotal++;
			itr = MIdmap.find(mid_line);
			if (itr != MIdmap.end())
			{
				pList[nCount++] = itr->second;

			}

			
		}




	}


    if (LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID, &BlockPtr, &LinePtr))
	{
		group = GetGroup(LinePtr);
		header_handle = group->header_handle;
		thread_handle = group->thread_handle;
    
		if (header_handle) 
		{
			headers = lock_headers (header_handle, thread_handle);

			for (index = 0 ;  index < group->total_headers && headers; index++) 
			{
				ThisHeader = header_elt (headers, index);

				//see if this art should be selected
				for (int i = 0; i < nCount; i++)
				{
					if (ThisHeader->number == pList[i] )
					{
						SelectHeader (Doc, ThisHeader, 1);
						num_affected++;
						break;
					}
				}

			}

			unlock_headers (header_handle, thread_handle);
		}
		UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
	}

	delete [] pList;

	if (pnTotal)
		*pnTotal = nTotal;
		

	return num_affected;



}



/*
char * XMLEncode( char * szLine)
{

	&quot;
	&apos;
	&amp;
	&lt;
	&gt;

}
*/
