/********************************************************************
 *                                                                  *
 *  MODULE    :  WVCODING.C                                         *
 *                                                                  *
 *  PURPOSE   : This file contains functions for en/decoding files  *
 *                                                                  *
 *  ENTRY POINTS:                                                   *
 *    		DecodeInit  - initializes decoding resources	    *
 *		DecodeDone  - frees decoding resources              *
 *		InitCoded   - initializes a coded block             * 
 *			      called for each new art to decode     *
 *		DecodeLine  - Main decoding engine.  Called once    *
 *			      for each input line                   *
 *		DecodeFile  - Uses file instead of comm input as a  *
 *   			      line provider to the decoding engine  *
 *		DecodeDoc   - Uses a Doc instead of comm input as a *
 *   			      line provider to the decoding engine  *
 *	CompleteThisDecode  - Called at end of each article - adds  *
 *			      block to appropriate decode thread,   *
 *			      writes to disk if possible, etc       *
 *                  Encode  - Encodes a file into a text block      *
 *            EncodeToFile  - Calls Encode, then writes block to    * 
 *                            an output file                        *
 *                                                                  *
 *  NOTES:                                                          *
 *	Both the max # of batched decode files and the max #        * 
 *	threads per file are fixed.  This should be made dynamic    *
 *	at some point I guess.                                      *
 *                                                                  *
 * Author: John S. Cooper (jcooper@netcom.com)                      *
 *   Date: Sept 13, 1993                                            *
 *                                                                  *
 * Let me know if you like this code, or if you re-use any piece of *
 * it. I'm just curious to know what people think.                  *
 ********************************************************************/
/* 
 * $Id: wvcoding.c 1.12 1994/09/18 22:44:20 jcooper Exp $
 * $Log: wvcoding.c $
 * Revision 1.12  1994/09/18  22:44:20  jcooper
 * Better handling of status windows, using IsBusy flag, etc
 *
 * Revision 1.11  1994/09/16  00:58:48  jcooper
 * new coding-status window style.  optimized info-header parser.
 * general cleanup for 92.6
 * 
 * Revision 1.10  1994/08/24  18:00:29  jcooper
 * misc encoding/decoding changes
 *
 * Revision 1.9  1994/08/11  21:03:19  rushing
 * FindExecutable fix for NT
 *
 * Revision 1.8  1994/08/11  20:16:25  rushing
 * bug fix
 *
 * Revision 1.6  1994/07/25  20:05:13  jcooper
 * execution of decoded files
 *
 * Revision 1.5  1994/05/23  18:36:00  jcooper
 * new attach code, session [dis]connect
 *
 * Revision 1.4  1994/03/01  19:14:22  rushing
 * ifdef'd the DEBUG info
 *
 * Revision 1.3  1994/02/24  21:28:04  jcoop
 * jcoop changes.
 *
 * Revision 1.2  1994/02/09  18:01:08  cnolan
 * cnolan 90.2 changes
 *
 * Revision 1.1  1994/01/18  09:54:20  jcoop
 * Initial revision
 *
 */ 
#include <windows.h>
#include <windowsx.h>
#include "wvglob.h"
#include "winvn.h"
#pragma hdrstop
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>		/* for isspace, isalnum, etc */

#ifdef _DEBUG
#define DEBUG_MAP
#define DEBUG_MEM
#define DEBUG_DECODE
#endif

#define LOW		1	// sequence confidence levels
#define HIGH            2
#define UNUSED		127	// unused mapping slots

#define END_BLOCK	2
#define CODE_LINE_LEN	45	
/*
 * Globals for this func
 */
char
	xxTable[CODINGTABLESIZE] = "+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
char
	uuTable[CODINGTABLESIZE] = "`!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
char
	base64Table[CODINGTABLESIZE] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char	*codingTable;
char 	customTable[CODINGTABLESIZE];

TypDecodeThread *threadList[MAX_DECODE_THREADS];
int	numDecodeThreads;		// number of active decode threads
int	currentDecodeThread;
int	numDumbDecoded;
BOOL    bossanovaMIME;                  // = TRUE if found MIME Version header
int  	thisNumBlocks; 			// MIME vars for current block
char 	thisContentType[80];
char	thisContentDesc[MAXINTERNALLINE];
char	thisBoundary[MAXINTERNALLINE];
int	thisContentEncoding, prevContentEncoding;
char 	prevBlockIdent[MAXFILENAME];
/*
 * Private functions
 */
int 	EncodeLine (unsigned char *outLine, unsigned char *line, int start, int num);
void	EncodeUnit (unsigned char *out, unsigned char *in, int num);
int	GetThreadByName (char *name);
int	AddToThreadList ();
void 	DestroyThread (int num);
void	DestroyCodedBlock (TypCoded **thisCoded);
void	InsertBlockInThread (int num, TypCoded *thisBlock, int index);
void	DeleteBlockFromThread (int num, int index);
BOOL	WriteDecodeThread(int num);
BOOL	WriteBlockToFile(int num, TypCoded *thisDecode);
BOOL	WriteTopBlockToFile (int num, int *endFlag);
BOOL	AddDataToBlock (TypCoded *thisDecode, char *newData, unsigned int dataLen);
void	ParseInfoLine (TypCoded *thisDecode, char *line, BOOL guessIdent);
BOOL	ParseAppSpecificLine (TypCoded *thisDecode, char *line);
char 	*ReadSubjectToken (char *dest, char **ptr);
BOOL	isnumber (char *str);
BOOL	SearchThreadNames (char *name);
int	ParseMimeHeaderLine (TypCoded *thisDecode, char *line);
int	ParseMimeContentType (TypCoded *thisDecode, char *line);

void CreateCodingStatusWnd();
int	DecodeDataLine (TypCoded *decode, char *line);
void 	CreateUUTable();
BOOL	TestIgnoreLine (char *line);
int	IsDataLine (char *line);
BOOL 	TestDataLine (char *line);
int 	ThreadTable (char *dest, char *name);
void	UpdateThreadStatus (int num, char *str);

void	ExecuteDecodedFile (int num, char *fileName);

#if defined(DEBUG_DECODE) || defined(DEBUG_MEM)
#define DEBUG_FILE "coding.log"
void 	DebugLog();
void 	InitDebugLog();
char 	debug[MAXCOMMLINE];
OFSTRUCT debugFileStruct;
#endif

#if defined(DEBUG_DECODE) || defined(DEBUG_MEM)
/* ------------------------------------------------------------------------
 * Debug log handler
 * Open/close the file for every addition to log, so all info is safely
 * captured in case of GPF
 */
void
DebugLog ()
{
	HFILE hDebugFile;

        if ((hDebugFile = OpenFile ((char far *)NULL, &debugFileStruct, OF_REOPEN|OF_WRITE)) >= 0)
	{       
		_llseek(hDebugFile, 0L, 2);	/* go to end */
        	_lwrite(hDebugFile, debug, strlen(debug));
        	_lclose(hDebugFile);
        }
}
void
InitDebugLog ()
{
	HFILE hDebugFile;

        if ((hDebugFile = OpenFile ((char far *)DEBUG_FILE, &debugFileStruct, OF_CREATE)) >= 0)
	{
        	sprintf(debug, "New log");
        	_lwrite(hDebugFile, debug, strlen(debug));
        	_lclose(hDebugFile);
        }
}
#endif
/* ------------------------------------------------------------------------
 * Decode a file
 * Uses a file instead of the comm article as a line-provider to the 
 * decode routines
 */
void
DecodeFile (HWND hParentWnd)
{
	FILE *DecodeFile;
        char mybuf[MAXINTERNALLINE], fileName[MAXFILENAME], *ptr;
        int result;

        if (AskForExistingFileName (hParentWnd, fileName, "Open encoded file") == FAIL)
        	return;

	DecodeInit();
	if ((DecodeFile = fopen (fileName, "r")) == NULL)
	{
		MessageBox (hParentWnd, "Failure to read file", "File error", MB_OK);
		return;
	} 
	if ((currentCoded = InitCoded(hParentWnd)) == NULL)
	{
		MessageBox (hParentWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
		return;
	}
       	while (1)
       	{
       		if ((ptr = fgets(mybuf, MAXINTERNALLINE, DecodeFile)) == NULL)
       			strcpy (mybuf, "EOF");	// make sure wrap up occurs

		result = DecodeLine (currentCoded, mybuf);
		if (result == FAIL)
		{
		     	MessageBox (hParentWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
			goto abortDecodeFile;
		}   		
		if (result == END_BLOCK)
		{
			if (CompleteThisDecode () == FAIL)
			{
			     	MessageBox (hParentWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
				goto abortDecodeFile;
			}   		
	   		if ((currentCoded = InitCoded(hParentWnd)) == NULL)
			{
				MessageBox (hParentWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
				goto abortDecodeFile;
			}
		}
		if (ptr == NULL)	// EOF
			break;
	}

	if (currentCoded != NULL)	// finish final block
		CompleteThisDecode ();
   
   abortDecodeFile:;
      	fclose(DecodeFile);
	DecodeDone ();
}
/* ------------------------------------------------------------------------
 * Decode a doc
 * Uses a doc instead of the comm article as a line-provider to the 
 * decode routines
 */
void
DecodeDoc (HWND hParentWnd, TypDoc *Document)
{
        int result;
	TypBlock far *BlockPtr;
	TypLine far *LinePtr;
	        
	DecodeInit();
	if ((currentCoded = InitCoded(hParentWnd)) == NULL)
	{
		MessageBox (hParentWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
		return;
	}
	
	LockLine (Document->hFirstBlock, sizeof (TypBlock), (TypLineID) 0L, &BlockPtr, &LinePtr);
	while (LinePtr->length != END_OF_BLOCK)
	{
		result = DecodeLine (currentCoded, 
				((char far *) LinePtr) + sizeof (TypText) + sizeof (TypLine));

		if (result == FAIL)
		{
		     	MessageBox (hParentWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
			goto abortDecodeDoc;
		}   		

		if (result == END_BLOCK)
		{
			if (CompleteThisDecode () == FAIL)
			{
			     	MessageBox (hParentWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
				goto abortDecodeDoc;
			}   		
	   		if ((currentCoded = InitCoded(hParentWnd)) == NULL)
			{
				MessageBox (hParentWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
				return;
			}
		}
		NextLine (&BlockPtr, &LinePtr);
	}
	if (currentCoded != NULL)	// finish final block
		CompleteThisDecode ();
   
   abortDecodeDoc:;
	GlobalUnlock (BlockPtr->hCurBlock);
	DecodeDone ();
}
/* ------------------------------------------------------------------------
 *	Encode a file and ask the user for a file to name to store it in
 */
void
EncodeToFile (HWND hParentWnd, char *inFile)
{
   register unsigned long i;
   char fileName[MAXFILENAME];
   OFSTRUCT outFileStruct;
   HFILE hAttachFile;
   TypTextBlock *encodeData;
 
   if ((encodeData = InitTextBlock (hParentWnd)) != NULL)
   {
      CreateStatusArea(hParentWnd);
      strcpy (currentCoded->ident, inFile);	// status window info

      if (Encode (encodeData, inFile, ADD_TO_FILE) != FAIL)
      {
	fileName[0]='\0';		
	if (AskForNewFileName (hParentWnd, fileName, "", FALSE) != FAIL)
	{
	   if ((hAttachFile = OpenFile ((char far *) fileName, &outFileStruct, OF_CREATE)) < 0)
		MessageBox(hParentWnd, "Unable to open output file", "File Error", MB_OK|MB_ICONSTOP);
	   else
	   {
	      for (i = 0; i < encodeData->numLines; i++)
		  _lwrite (hAttachFile, TextBlockLine (encodeData, i), lstrlen(TextBlockLine (encodeData, i)));
	      _lclose (hAttachFile);
	   }
	}	
      }     
      FreeTextBlock (encodeData);
      DestroyStatusArea ();
   }   
}

/* ------------------------------------------------------------------------
 *	Create a status window a coding block used for status info
 *	the currentCoded block is purely for status in this case
 *	This is used by attach and EncodeFile and ReadFileToTextBlock
 *	to show status in progress
 *	Use UpdateBlockStatus to update/display status
 *	Use DestroyStatusArea to clean up when done
 */
void
CreateStatusArea (HWND hParentWnd)
{
	if ((currentCoded = InitCoded(hParentWnd)) == NULL)
		return;
	CreateCodingStatusWnd();
	currentCoded->sequence = 1;		// status window info

}

void
DestroyStatusArea ()
{
	DestroyCodedBlock (&currentCoded);
	DestroyWindow (hCodedBlockWnd);
	hCodedBlockWnd = (HWND)NULL;
}

void
CreateCodingStatusWnd()
{
	/* Create coding status window, top center */
	hCodedBlockWnd = CreateWindowEx (WS_EX_DLGMODALFRAME, "WinVnBlockCoding", "Block Encoding Status",
			WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_THICKFRAME,
			(xScreen-STATUSWIDTH)>>1, 1, STATUSWIDTH, STATUSHEIGHT,
			(HWND)NULL, (HMENU)NULL, hInst, (void far *)NULL);
	
	SetHandleBkBrush( hCodedBlockWnd, hStatusBackgroundBrush);
	ShowWindow (hCodedBlockWnd, SW_SHOWNORMAL);
}


/* ------------------------------------------------------------------------
 *	Adds text to a thread coding status block, and refreshes
 *	the thread status window
 */	
void
UpdateThreadStatus (int num, char *str)
{
	AddLineToTextBlock (threadList[num]->statusText, str);
	InvalidateRect (threadList[num]->statusText->hTextWnd, NULL, FALSE);
	UpdateWindow (threadList[num]->statusText->hTextWnd);
}

/* ------------------------------------------------------------------------
 *	Refreshes the block status window
 */	
void
UpdateBlockStatus ()
{
	InvalidateRect (hCodedBlockWnd, NULL, FALSE);
	UpdateWindow (hCodedBlockWnd);
}

/* ------------------------------------------------------------------------
 *	Encode a file 
 *	Called _after_ the attach dialog, so all globals for this encode
 *	are set  (sorry about the globals...)
 *	Result is encoded file in TextBlock 
 *	Assumes currentCoded is initialized
 */
int 
Encode (TypTextBlock *textBlock, char *fileName, int mode)
{
	HFILE hFile;
        int numRead, lineLen, start;
	register int i;
	unsigned char inBuf[CODE_LINE_LEN];
	unsigned char outLine[MAXINTERNALLINE];
	extern char *NameWithoutPath ();

	CodingState = ATTACH_PROCESSING;

	// Set up encoding map
	switch (EncodingTypeNum)
	{
	case CODE_BASE64:	
		codingTable = base64Table;
		break;
	case CODE_UU:
		codingTable = uuTable;
		break;
	case CODE_XX:
//	Note, XX table is hard coded in declarations for simplicity
		codingTable = xxTable;
		break;
	case CODE_CUSTOM:
		codingTable = UserCodingTable;
		// add 2 table lines
		if (AddEndedLineToTextBlock (textBlock, "table", mode))
			return (FAIL);
		memmove (str, UserCodingTable, 32);
		str[32] = '\0';
		if (AddEndedLineToTextBlock (textBlock, str, mode))
			return (FAIL);
		memmove (str, &UserCodingTable[32], 32);
		if (AddEndedLineToTextBlock (textBlock, str, mode))
			return (FAIL);
		break;
	}

	// Prepare for encoding
	switch (EncodingTypeNum)
	{
	case CODE_UU:
	case CODE_XX:
	case CODE_CUSTOM:
		// these are 3-to-4 encodings with 1st char indicating line len
		// and block starts with 'begin' line and ends with 'end' line
		// add 'begin' line
		NameWithoutPath (str, fileName);
		sprintf (outLine, "begin 755 %s", str);
		if (AddEndedLineToTextBlock (textBlock, outLine, mode))
			return (FAIL);
		currentCoded->numLines++;
	        // set 1st char to appropriate line length value
	        outLine[0] = codingTable[CODE_LINE_LEN];
	        start = 1;	// count includes the count char itself
		break;
	case CODE_BASE64:
		// base64 is a 3-to-4 encoding, with no line len indicator and 
		// no 'begin' or 'end' lines
		start = 0;
	}
	lineLen = CODE_LINE_LEN;

	// open the file and have at it	
        hFile = _lopen (fileName, OF_READ);
    	while (1)
    	{
    		if ((numRead = _lread(hFile, inBuf, lineLen)) == 0)
    			break;
		if (numRead < lineLen)	// last line length value
		{
			if (EncodingTypeNum != CODE_BASE64)
				outLine[0] = codingTable[numRead];
			
			for (i = numRead; i < lineLen; i++)
				inBuf[i] = 0;
		}
    		EncodeLine (outLine, inBuf, start, numRead);
		if (AddEndedLineToTextBlock (textBlock, outLine, mode))
			return (FAIL);
		currentCoded->numLines++;
		currentCoded->numBytes += numRead;
	    	if (currentCoded->numLines % STATUS_UPDATE_FREQ == 0)
	    		UpdateBlockStatus ();
	}    	
	_lclose(hFile);
	switch (EncodingTypeNum)
	{
	case CODE_UU:
	case CODE_XX:
	case CODE_CUSTOM:
	        // add zero length line
	        outLine[0] = codingTable[0];
	        outLine[1] = '\0';
		if (AddEndedLineToTextBlock (textBlock, outLine, mode))
			return (FAIL);
		// add 'end' line
		strcpy (outLine, "end");
		if (AddEndedLineToTextBlock (textBlock, outLine, mode))
			return (FAIL);
		currentCoded->numLines+=2;
        }
	UpdateBlockStatus ();
	
	CodingState = INACTIVE;
	return (SUCCESS);
}
	
/* ------------------------------------------------------------------------
 *	Encode num chars from line
 *	Put data in outLine starting at start
 */
int 
EncodeLine (unsigned char *outLine, unsigned char *line, int start, int num)
{
	register int i, j;
	
	for (j = start, i = 0; i < num; j+=4, i+=3)
		EncodeUnit (&outLine[j], &line[i], (i+3 > num) ? num - i : 3);

	outLine[j] = '\0';
	return (j);
}
	
void
EncodeUnit (unsigned char *out, unsigned char *in, int num)
{
       	out[0] = codingTable[((in[0]>>2) & 63)];
	out[1] = codingTable[(((in[0]<<4)|(in[1]>>4)) & 63)];
	if (num == 1)
	{
		if (EncodingTypeNum == CODE_BASE64)
		    strcpy (&out[2], "==");
		return;
	}

	out[2] = codingTable[(((in[1]<<2)|(in[2]>>6)) & 63)];
	if (num == 2)
	{
		if (EncodingTypeNum == CODE_BASE64)
		    strcpy (&out[3], "=");
		return;
	}

	out[3] = codingTable[(in[2] & 63)];
}
/* ------------------------------------------------------------------------
 *	Init for decoding
 * 	Initialize the coding tables
 *	Init threadList:  list of pointers to Thread lists 
 *	(one thread for each file being decoded)
 *		
 */
void
DecodeInit ()
{
	register int i;
	
#ifdef DEBUG_MEM
	InitDebugLog();
#endif
	
        for (i = 0; i < MAX_DECODE_THREADS; i++)
        	threadList[i] = NULL;

	currentCoded = (TypCoded *) NULL;
	CodingState = DECODE_SKIPPING;
	numDecodeThreads = 0;
	numDumbDecoded = 0;
	currentDecodeThread = -1;	
	thisNumBlocks = -1;
	prevBlockIdent[0] = '\0';
	thisContentEncoding = CODE_UNKNOWN;
	prevContentEncoding = CODE_UNKNOWN;
        customTable[0] = '\0';

#ifdef DEBUG_MEM
	sprintf(debug, "\nInitialized decoding, %d threads", MAX_DECODE_THREADS);
	DebugLog();
#endif
	// Create coding status window (open for duration of coding only)
	CreateCodingStatusWnd();
	UpdateBlockStatus ();
}

/* ------------------------------------------------------------------------
 * At this point, all threads in sequence should have been written and freed,
 * If any threads left, we either had an incomplete decode of some file,
 * or the file was encoded w/out sequence info, and we were waiting to 
 * hopefully receive it all.  If we had an endflag, then write it in order
 * received
 * Accepts handle of parent window so it can display any 'incomplete' messages
 */
void
DecodeDone ()
{
	char mybuf[MAXINTERNALLINE], name[MAXINTERNALLINE];

#ifdef DEBUG_MEM
	sprintf(debug, "\nDone coding,  Wrapping up stray threads");
	DebugLog();
#endif
	name[0]='\0';			// only show name if not verbose mode
	while (numDecodeThreads > 0)	// always work on top of list [0]
	{
		if (!CodingStatusVerbose)
			if (threadList[0]->name[0] != '\0')
				sprintf (name, "%s   ", threadList[0]->name);
			else
				sprintf (name, "%s   ", threadList[0]->ident);

		if (threadList[0]->numBlocks == 0)
		{	// premature end of file
                	WriteDecodeThread (0);
			sprintf (mybuf, "%sDecode is missing parts, written total size %ld", name, threadList[0]->totalBytes);
			UpdateThreadStatus (0, mybuf);
		}
		else if (threadList[0]->contentEncoding != CODE_BASE64 &&
		        !threadList[0]->codedBlockList[threadList[0]->numBlocks-1]->endFlag)
		{
			sprintf (mybuf, "%sEnd never found.  Cancelling", name);
			UpdateThreadStatus (0, mybuf);
		}
		else if (threadList[0]->expectedNumBlocks > 0)
		{	// there IS sequencing info, so we should not be here
                	WriteDecodeThread (0);
			sprintf (mybuf, "%sDecode is missing parts, written total size %ld", name, threadList[0]->totalBytes);
			UpdateThreadStatus (0, mybuf);
		}
		else
                {
			sprintf (mybuf, "%sCompleteness confidence is medium, written total size %ld", name, threadList[0]->totalBytes);
			UpdateThreadStatus (0, mybuf);
                	WriteDecodeThread (0);
		}			
		DestroyThread (0);	// decrements numDecodeThreads
	}
	currentCoded = NULL;
	CodingState = INACTIVE;
        CommDecoding = FALSE;
	DestroyWindow (hCodedBlockWnd);
	hCodedBlockWnd = (HWND)NULL;
}	

/* ------------------------------------------------------------------------
 * 	Initialize a coded object, and initial data space
 *	Returns ptr to object or NULL if failed
 */
TypCoded *
InitCoded (HWND hParentWnd)
{      
	TypCoded *thisCoded = NULL;
	
#ifdef DEBUG_MEM
	sprintf(debug, "\nInitializing a coded object");
	DebugLog();
#endif
	if ((thisCoded = (TypCoded *) GlobalAllocPtr ( GMEM_MOVEABLE, sizeof (TypCoded))) == NULL)
		return ((TypCoded *) NULL);
	
#ifdef DEBUG_MEM
	sprintf(debug, "\nAllocing data size %d", BASE_BLOCK_SIZE);
	DebugLog();
#endif
	if ((thisCoded->data = (char huge *) GlobalAllocPtr (GMEM_MOVEABLE, BASE_BLOCK_SIZE * sizeof (char))) == NULL)
		return ((TypCoded *) NULL);

	thisCoded->maxBytes = BASE_BLOCK_SIZE;
	thisCoded->numBytes = 0;
	thisCoded->numLines = 0;
	thisCoded->sequence = -1;
	thisCoded->seqConfidence = 0;
	thisCoded->name[0] = '\0';
	thisCoded->ident[0] = '\0';
	thisCoded->endFlag = thisCoded->beginFlag = FALSE;
	thisCoded->hParentWnd = hParentWnd;
        bossanovaMIME = FALSE;
	
	return (thisCoded);
}	

/* ------------------------------------------------------------------------
 * 	Destroy a thread
 *	Destroy all blocks in the thread
 *	Remove the thread from the threadList
 */
void
DestroyThread (int num)
{ 
	register int i;
	
#ifdef DEBUG_MEM
	sprintf(debug, "\nDestroying thread %d", num);
	DebugLog();
#endif
	if (CodingStatusVerbose)
		threadList[num]->statusText->IsBusy = FALSE;
	
	for (i = 0; i < threadList[num]->numBlocks; i++)
		DestroyCodedBlock (&(threadList[num]->codedBlockList[i]));
		
	GlobalFreePtr (threadList[num]);

	for (i = num; i < numDecodeThreads; i++)
		threadList[i] = threadList[i + 1];
	
	numDecodeThreads--;
	if (currentDecodeThread == num)
		currentDecodeThread = max (0, currentDecodeThread - 1);
}
/* ------------------------------------------------------------------------
 * 	Destroy a coded block structure
 *	Free the huge data.  Takes a ptr to a ptr so it can NULL it when done
 */
void
DestroyCodedBlock (TypCoded **thisCoded)
{		
#ifdef DEBUG_MEM
	sprintf(debug, "\nDestroying block sequence %d", (*thisCoded)->sequence);
	DebugLog();
#endif
	GlobalFreePtr ((*thisCoded)->data);
	GlobalFreePtr (*thisCoded);
        *thisCoded = NULL;
}

/* ------------------------------------------------------------------------
 * 	Given a file name, find the associated thread number  
 *	Returns -1 if no thread by that name exists
 */
int
GetThreadByName (char *name)
{
	int i, result;
	
	for (result = -1, i = 0; i < numDecodeThreads && result == -1; i++)
		if (!_stricmp (name, threadList[i]->name))
			result = i;

	return (result);
}
/* ------------------------------------------------------------------------
 * 	Determine if a thread has already been started for this ident
 *	if yes, add decode object to that thread
 *	otherwise, alloc a new thread list structure, add this block to it
 *	At end, currentDecodeThread is set to handle of thread
 */
BOOL                                                           
AddToThreadList ()
{
	register int i;
	int num;
	char mybuf[MAXINTERNALLINE];
	int x, y, width, height, maxX, maxY;
	
 	if (DumbDecode)
 	{	
		if (numDecodeThreads == 0)
		 	num = -1;
		else 
		 	num = 0;
	} else
 	/* Search existing threads for an ident match.  If
 	 * no match found, or no threads exist yet, add new thread.
 	 */
		for (num = -1, i = 0; i < numDecodeThreads; i++)
			if (!_stricmp (currentCoded->ident, threadList[i]->ident))
				num = i;

	if (num == -1)		/* add a new thread */
	{
		if ((numDecodeThreads + 1) > MAX_DECODE_THREADS)	
		{
			return (FAIL);
/*			GlobalUnlock(decodeObjectHandles);
  			decodeObjectHandles = GlobalRealloc (decodeFileHandles,
  				(numDecodeThreads + LIST_SIZE_INC) * sizeof (HGLOBAL), 
  				GMEM_MOVEABLE);
			threadList = (HGLOBAL *) GlobalLock (decodeFileHandles);
*/
		}

// create a new DecodeThreadStruct object, store its handle in threadList,
#ifdef DEBUG_MEM
		sprintf(debug, "\nCreating new thread ident %s", currentCoded->ident);
		DebugLog();
#endif
 		num = numDecodeThreads;
		numDecodeThreads++;

		threadList[num] = (TypDecodeThread *) GlobalAllocPtr (GMEM_MOVEABLE, sizeof (TypDecodeThread));
		threadList[num]->expectedNumBlocks = 0;
		threadList[num]->numBlocksWritten = 0;
		threadList[num]->numBlocks = 0;
		threadList[num]->totalBytes = 0;
		threadList[num]->dosFileName[0] = '\0';
		if (thisContentEncoding == CODE_CUSTOM)
		    strncpy (threadList[num]->customTable, customTable, CODINGTABLESIZE);
		threadList[num]->contentEncoding = thisContentEncoding;
		strcpy (threadList[num]->ident, currentCoded->ident);
                if (currentCoded->name[0] != '\0')
			strcpy (threadList[num]->name, currentCoded->name);
		else
			threadList[num]->name[0] = '\0';

        	if (CodingStatusVerbose || NumStatusTexts == 0)
		{
		   if ((threadList[num]->statusText = InitTextBlock(hCodedBlockWnd)) == NULL)
			MessageBox (hCodedBlockWnd, "Memory allocation error in text block", "Memory Error", MB_OK);
                
                   if (NumStatusTexts + 1 > MAX_DECODE_THREADS);
                   	// ?????
                
                   CodingStatusText[NumStatusTexts++] = threadList[num]->statusText;

		   if (CodingStatusVerbose)
	   		sprintf(mybuf, "Decoding Status for file %s", (threadList[num]->name[0]!='\0')?threadList[num]->name:threadList[num]->ident);
		   else
		   	strcpy(mybuf, "Decoding Status");
		   
		   width = xScreen>>1;
		   height = (yScreen>>1) - CaptionHeight;
		   maxX = 3 * (width>>1);	/* 3/4 screen width  */
		   maxY = 3 * (height>>1);	/* 3/4 screen height */

		   x = (NumStatusTexts * 10) % maxX;
		   y = (yScreen>>2) + (NumStatusTexts * CaptionHeight) % maxY;

		   threadList[num]->statusText->hTextWnd =
			CreateWindow ("WinVnCoding", mybuf,
				WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
				x, y, width, height,
				(HWND)NULL, (HMENU)NULL, hInst, (void far *)NULL);

		   if (threadList[num]->statusText->hTextWnd == (HWND)NULL)
		   {
			FreeTextBlock(threadList[num]->statusText);	
			MessageBox (hCodedBlockWnd, "Couldn't create status text window","Window Creation Error", MB_OK);
			return (FAIL);			
		   }
		   SetHandleBkBrush (threadList[num]->statusText->hTextWnd,
				  	hStatusBackgroundBrush);
		   ShowWindow (threadList[num]->statusText->hTextWnd, SW_SHOWNORMAL);
        	   UpdateWindow (threadList[num]->statusText->hTextWnd);
		}
		else
		   threadList[num]->statusText = CodingStatusText[0];

	}
	
/* If this new block enlightens us with any useful thread info, update
 * the thread
 */
	if (currentCoded->name[0] != '\0')
		strcpy (threadList[num]->name, currentCoded->name);
//	if (threadList[num]->contentType[0] == '\0')
//		strcpy (threadList[num]->contentType, thisContentType);
	if (threadList[num]->expectedNumBlocks == 0 && thisNumBlocks > 0)
		threadList[num]->expectedNumBlocks = thisNumBlocks;
	
	threadList[num]->totalBytes += currentCoded->numBytes;
/*
 * Now, add this decode object to the thread codedBlockList
 * Insert in correct sequence. 
 * If no sequence info available, add to end of list, but before any end block
 */
	if (DumbDecode)
		InsertBlockInThread(num, currentCoded, threadList[num]->numBlocks);
	else
	{	
	  if (threadList[num]->numBlocks != 0 && currentCoded->sequence == -1)
	  {	/* sequence unknown, add at end or before end block */
		if (currentCoded->beginFlag)
			InsertBlockInThread(num, currentCoded, 0);
		else 
		{
			if (threadList[num]->codedBlockList[threadList[num]->numBlocks - 1]->endFlag)
				InsertBlockInThread(num, currentCoded, threadList[num]->numBlocks - 1);
			else			
				InsertBlockInThread(num, currentCoded, threadList[num]->numBlocks);
		}
	  }
	  else
	  {
		for (i = 0; i < threadList[num]->numBlocks; i++)
		{
			if (threadList[num]->codedBlockList[i]->sequence == -1 || 
			    threadList[num]->codedBlockList[i]->sequence >= currentCoded->sequence)
				break;
		}
		InsertBlockInThread (num, currentCoded, i);
	  }                                                  
	}
	currentDecodeThread = num;	 	
	if (!CodingStatusVerbose)
		return (SUCCESS);
	
	sprintf (mybuf, "Decoded block");
	if (currentCoded->sequence == -1)
	{
		if (currentCoded->beginFlag || currentCoded->endFlag)
		{
			if (currentCoded->beginFlag)
				strcat (mybuf, ", begin block");
			if (currentCoded->endFlag)
				strcat (mybuf, ", end block");
		} else
			strcat (mybuf, " sequence unknown");
	}		
	else
	{
		sprintf (str, " %d", currentCoded->sequence);
		strcat (mybuf, str);
		if (threadList[num]->expectedNumBlocks > 0)
		{
			sprintf (str, " of %d.", threadList[num]->expectedNumBlocks);
			strcat (mybuf, str);
		}
		if (currentCoded->sequence == threadList[num]->numBlocksWritten + 1)
			strcat (mybuf, " In sequence");
		else
			strcat (mybuf, " Out of sequence");
	}
	UpdateThreadStatus (num, mybuf);
	thisNumBlocks = -1;

	return (SUCCESS);
}

/* ------------------------------------------------------------------------
 * 	Insert/delete a block in a thread list
 *	Inserts and deletes will be O(n).  Since n will never be big,
 *	it's not worth the trouble (and space usage) of using a linked list
 *
 *	index should never be -1 (case of unkown sequence handled by caller)
 *	to insert at front of list, index = 0
 *	to insert at end of list, index = threadList[num]->numBlocks
 */
void
InsertBlockInThread (int num, TypCoded *block, int index)
{
	register int i;
	
	for (i = threadList[num]->numBlocks; i > index && i > 0 ; i--)
		threadList[num]->codedBlockList[i] = threadList[num]->codedBlockList[i-1];
 	
#ifdef DEBUG_DECODE
	sprintf(debug, "\nInserting into thread %s at posn %d", threadList[num]->ident, i);
	DebugLog(); 
#endif
 	threadList[num]->codedBlockList[i] = block;
			
	threadList[num]->numBlocks++;		

	return;
}

void
DeleteBlockFromThread (int num, int index)
{
	register int i;
	
	for (i = index; i < threadList[num]->numBlocks; i++)
		threadList[num]->codedBlockList[i] = threadList[num]->codedBlockList[i+1];
 	
#ifdef DEBUG_DECODE
	sprintf(debug, "\nRemoved from thread %s at posn %d", threadList[num]->name, index);
	DebugLog();
#endif
	threadList[num]->numBlocks--;		

	return;
}

/* ------------------------------------------------------------------------
 * 	Complete a decode block
 *	Adds to thread list
 *	Writes block to disk if possible (and any others now in sequence)
 *	If it ends a thread, make sure everything is written, and
 *	free up the thread
 */
BOOL
CompleteThisDecode ()
{
	char mybuf[MAXINTERNALLINE];
	int num, singleBlockDone, foundEnd;

	UpdateBlockStatus ();	// final size displayed
#ifdef DEBUG_DECODE
	sprintf(debug, "\nCompleteThisDecode for file %s, block %d",
			currentCoded->ident, currentCoded->sequence);
	DebugLog();
#endif
	if (currentCoded->numBytes == 0)
	{
#ifdef DEBUG_DECODE
		sprintf(debug, "\nNon-data block.  Discarding it.");
		DebugLog();
#endif
		DestroyCodedBlock (&currentCoded);
		return (SUCCESS);
	}
	
	if (DumbDecode)
	{		
		if (numDumbDecoded == 0 && !currentCoded->beginFlag)
			return (SUCCESS); // Dumb skipping a data block
		if (numDumbDecoded > 0 && currentCoded->beginFlag)
		{		// found next begin without an end
			if (CodingStatusVerbose)
				sprintf (mybuf, "Decode missing end, total size %ld, wrote to file %s", threadList[num]->totalBytes, threadList[num]->dosFileName);
			else
				sprintf (mybuf, "%s   Decode missing end, total size %ld, wrote to file %s", 
			threadList[num]->name, threadList[num]->totalBytes, threadList[num]->dosFileName);
			UpdateThreadStatus (num, mybuf);
			DestroyThread(num);
			numDumbDecoded = 0;
		}
	}					
        else
	// if for some reason (?) we didn't get a begin, but this is block 1
	if (currentCoded->sequence == 1 && 
	    currentCoded->seqConfidence == HIGH && 
	    !currentCoded->beginFlag)
	{
		currentCoded->beginFlag;
		strcpy (currentCoded->name, currentCoded->ident);
	}    
	
	if (AddToThreadList () == FAIL)
		return (FAIL);
	
	prevContentEncoding = thisContentEncoding;
	thisContentEncoding = CODE_UNKNOWN;
	
	num = currentDecodeThread;	
	singleBlockDone = currentCoded->beginFlag && currentCoded->endFlag;

	if (!CodingStatusVerbose && 
	   (threadList[num]->numBlocksWritten == 0 && threadList[num]->numBlocks == 1))
	{
		sprintf (mybuf, "%s   Decode in progress", (threadList[num]->name[0]!='\0')?threadList[num]->name:threadList[num]->ident);
		UpdateThreadStatus (num, mybuf);
	}
	
	if (DumbDecode)
	{
		if (WriteTopBlockToFile (num, &foundEnd) == FAIL)
			return (FAIL);
		numDumbDecoded++;
	}
        else
        {
/* If this block is both begin and end, then go straight to WriteDecodeThread
 * If currentCoded is in sequence, then it was added as block 0 in the 
 * thread's block list, and may have caused other blocks to now be in 
 * sequence as well.  Write all blocks which are in sequence now.
 */
	  if (!singleBlockDone && currentCoded->sequence != -1 && 
	      currentCoded->seqConfidence != 0)
		while (threadList[num]->numBlocks != 0)
		{
			if (threadList[num]->codedBlockList[0]->sequence == threadList[num]->numBlocksWritten)
			{	// skip duplicate block sequence
				DestroyCodedBlock (&currentCoded);	// threadList[num]->codedBlockList[0]
        	                DeleteBlockFromThread (num, 0);
        	                break;
        	        }
			if (threadList[num]->codedBlockList[0]->sequence != threadList[num]->numBlocksWritten + 1)
				break;
#ifdef DEBUG_DECODE
			sprintf(debug, "\nBlock in sequence, writing it, file %s, block %d",
				threadList[num]->name, threadList[num]->codedBlockList[0]->sequence);
			DebugLog();
#endif
			if (WriteTopBlockToFile (num, &foundEnd) == FAIL)
				return (FAIL);
 		}
 	}	
	if (singleBlockDone || (foundEnd && DumbDecode) ||
	    (foundEnd && threadList[num]->expectedNumBlocks > 0 && 
	    threadList[num]->numBlocksWritten >= threadList[num]->expectedNumBlocks))
	{
#ifdef DEBUG_DECODE
		sprintf(debug, "\nThread complete: file %s", threadList[num]->name);
		DebugLog();
#endif
	   	if (WriteDecodeThread(num) == FAIL)
		{
			DestroyThread(num);
			currentCoded = NULL;
	   		return (FAIL);
	   	}

		if (CodingStatusVerbose)
			sprintf (mybuf, "Decode complete, total size %ld, wrote to file %s", threadList[num]->totalBytes, threadList[num]->dosFileName);
		else
			sprintf (mybuf, "%s   Decode complete, total size %ld, wrote to file %s", 
		threadList[num]->name, threadList[num]->totalBytes, threadList[num]->dosFileName);
		UpdateThreadStatus (num, mybuf);

		DestroyThread(num);
		numDumbDecoded = 0;
	}
	currentCoded = NULL;
	return (SUCCESS);
}			
			
/* ------------------------------------------------------------------------
 * 	Write data for the top block of a thread to a file, and remove
 *	the top block from the thread
 */
BOOL
WriteTopBlockToFile (int num, int *endFlag)
{
	char mybuf[MAXINTERNALLINE];

	if (WriteBlockToFile (num, threadList[num]->codedBlockList[0]) == FAIL)
	{
		sprintf (mybuf, "Could not write to file %s", threadList[num]->dosFileName);
		UpdateThreadStatus (num, mybuf);
		DestroyThread(num);
		return (FAIL);
	}
	
	*endFlag = threadList[num]->codedBlockList[0]->endFlag;
	DestroyCodedBlock (&(threadList[num]->codedBlockList[0]));
	DeleteBlockFromThread (num, 0);
	threadList[num]->numBlocksWritten++; 
	return (SUCCESS);
}
	
/* ------------------------------------------------------------------------
 * 	Write data for a thread to a file
 */
BOOL
WriteDecodeThread (int num)
{
	register int i;
	char mybuf[MAXINTERNALLINE];
		
	for (i = 0; i < threadList[num]->numBlocks; i++)
	{
#ifdef DEBUG_DECODE
		sprintf(debug, "\nSaving sequence %d", threadList[num]->codedBlockList[i]->sequence);
		DebugLog();
#endif
		if (WriteBlockToFile (num, threadList[num]->codedBlockList[i]) == FAIL)
		{
			sprintf (mybuf, "Could not write to file %s", threadList[num]->dosFileName);
			UpdateThreadStatus (num, mybuf);
			return (FAIL);
		}
	}
	if (ExecuteDecodedFiles)
		ExecuteDecodedFile(num, threadList[num]->dosFileName);

	return (SUCCESS);
}

/* ------------------------------------------------------------------------
 * 	Write block data to a file
 *	Verify thread file name will work in DOS, and request a new file
 *	name if necessary
 * 	Accepts handle of parent window to allow any necessary dialogs
 */
BOOL
WriteBlockToFile(int num, TypCoded *thisDecode)
{ 
	register unsigned long i;
	unsigned int chunkSize;
	char	actualFileName[MAXFILENAME], mybuf[MAXINTERNALLINE];
        OFSTRUCT outFileStruct;
	HFILE 	hWriteFile;
	UINT	fileMode;

#define	CHUNK_SIZE 65500	/* must not exceed UINT (65534) */		
	
	if (threadList[num]->dosFileName[0] != '\0') {
		fileMode = OF_WRITE;
		if (strchr (threadList[num]->dosFileName, '\\') == NULL)
		{
			strcpy (actualFileName, DecodePathName);
			if (actualFileName[strlen(actualFileName)-1] != '\\')
				strcat(actualFileName, "\\");
			strcat(actualFileName, threadList[num]->dosFileName);
        	}
		else
		   strcpy (actualFileName, threadList[num]->dosFileName);
	}
	else 
	{
	/* First time write to file
	 */
		fileMode = OF_CREATE;
                
                actualFileName[0] = '\0';
		if (threadList[num]->name[0] != '\0')
			strcpy (threadList[num]->dosFileName, threadList[num]->name);
		else if (threadList[num]->ident[0] != '\0')
			strcpy (threadList[num]->dosFileName, threadList[num]->ident);
		if (threadList[num]->dosFileName[0] != '\0')
		{
			strcpy (actualFileName, DecodePathName);
			if (actualFileName[strlen(actualFileName-1)] != '\\')
				strcat(actualFileName, "\\");
			strcat(actualFileName, threadList[num]->dosFileName);
		}

		/* smart filer work */
		if (SmartFile(hCodedBlockWnd, actualFileName) == FAIL)
			return (FAIL);

		strcpy (threadList[num]->dosFileName, actualFileName);
	}
        
        if ((hWriteFile = OpenFile ((char far *) actualFileName, &outFileStruct, fileMode)) < 0)
	{
		MessageBox(hCodedBlockWnd, "Unable to open output file", "File Error", MB_OK|MB_ICONSTOP);
		return (FAIL);
        }
        if (fileMode == OF_WRITE)
        	_llseek (hWriteFile, 0L, 2);	/* append */
        	
	for (i = 0L; i < thisDecode->numBytes; i += chunkSize)
	{
	       	if (i + CHUNK_SIZE > thisDecode->numBytes)
	       		chunkSize = (unsigned int) (thisDecode->numBytes - i);
	       	else
	       		chunkSize = CHUNK_SIZE;

	       	if (_lwrite(hWriteFile, &(thisDecode->data[i]), chunkSize) != chunkSize)
		{
			MessageBox(hCodedBlockWnd, "Error writing to file", "File Error", MB_OK|MB_ICONSTOP);
		       	_lclose(hWriteFile);
			return (FAIL);
	        }
        }

	if (CodingStatusVerbose)
	{
		strcpy (mybuf, "     Wrote block ");
		if (thisDecode->sequence > 0)
		{
			sprintf (str, "%d ", thisDecode->sequence);
			strcat (mybuf, str);
		}
//		sprintf (str, "to file %s", threadList[num]->dosFileName);
//		strcat (mybuf, str);
		UpdateThreadStatus (num, mybuf);
	}
       	_lclose(hWriteFile);
	return (SUCCESS);
}

/* ------------------------------------------------------------------------
 * 	Decode a line in article.  Store in thisDecode
 */
int  
DecodeLine(TypCoded *thisDecode, char *line)
{
	register int i;
	static int table_count;
	char thisBlockName[MAXINTERNALLINE];
     	int mode;

	if (strlen(line) == 0)	
		return (SUCCESS);

	thisDecode->numLines++;
	if (thisDecode->numLines % STATUS_UPDATE_FREQ == 0)
		UpdateBlockStatus ();
	
	switch (CodingState)
	{
	case DECODE_SKIPPING: 
		if (strlen(line) < 3)	// if we're skipping, ignore any really short line
			return (SUCCESS);

		// encoded UU/XX/Custom data always starts with line 
		// like 'begin <mode> <file-name>'
		if (!strncmp (line, "begin", 5) &&
		    sscanf (line, "%*s %d %s", &mode, thisBlockName) == 2)
                {
#ifdef DEBUG_DECODE
			sprintf(debug, "\nStart of new encoded file %s, mode %d", thisBlockName, mode);
			DebugLog();
#endif
			if (thisBlockName[0] != '\0')
				strcpy (thisDecode->name, strlwr(thisBlockName));
                        else
				strcpy (thisDecode->name, thisDecode->ident);
			                        
			thisDecode->sequence = 1;
			thisDecode->beginFlag = TRUE;
//			thisDecode->mode = mode;

			CodingState = DECODE_PROCESSING;
			return (SUCCESS);
		}     
/* Check for keywords in the non-data lines which may aid in describing
 * the name for this data, the sequence, a custom table, etc
 */
 		else if (!DumbDecode && !_strnicmp(line, "subject:", 8))
 			ParseInfoLine (thisDecode, &line[8], TRUE);
 		else if (!DumbDecode && !_strnicmp(line, "summary:", 8))
			ParseInfoLine (thisDecode, &line[8], FALSE);
 		else if (!DumbDecode && ParseAppSpecificLine (thisDecode, line))
			return (SUCCESS);
 		else if (!strncmp(line, "table", 5))
 		{	// next two lines are table
 			CodingState=DECODE_GET_TABLE;
			table_count = 0;
 			return (SUCCESS);
 		}
		 			
/*		else if (ParseMimeHeaderLine(thisDecode, line) == SUCCESS)
			return (SUCCESS);
*/
/* Skipping to find new block of data.
 * Skip until a data line (a line with the correct length) is found
 */		
 		else if (IsDataLine(line))
		{   
#ifdef DEBUG_DECODE
			sprintf(debug, "\nFound start of next data section", line);
			DebugLog();
#endif
                     	CodingState = DECODE_PROCESSING;
                       	// found good line, process it and continue processing
                       	return (DecodeDataLine(thisDecode, line));
		}
#ifdef DEBUG_DECODE
		sprintf(debug, "\nskipped: %s", line);
		DebugLog();
#endif
		return (SUCCESS);		// continue skipping 

	case DECODE_GET_TABLE:
	// This is one of two lines containing table info
		memmove ((customTable + table_count * 32), line, 32);

		if (++table_count == 2)
		{
#ifdef DEBUG_MAP
			strncpy (str, customTable, 64);
			str[64]='\0';
			sprintf(debug, "\nfound table: %s", str);
			DebugLog();
#endif
			CodingState = DECODE_SKIPPING;
			if (i = CreateCodingMap (CodingMap[CODE_CUSTOM], customTable) != -1)
			{
#ifdef DEBUG_MAP
				sprintf (debug, "Invalid decoding table in block.  Duplicate character %c.",  i);
				DebugLog();
#endif
				customTable[0]='\0';	// ditch the table
			} 
			else
				thisContentEncoding = CODE_CUSTOM;
		}
		return (SUCCESS);
			
	case DECODE_PROCESSING:
/* Some encoders place an END line at end of a section, and length it like 
 * a proper encoded line
 * Make sure we catch it, don't decode it as data, and switch back to skipping
 * Note the only End statement which means End of entire file is lower case
 * "end".  The other tags are ignored, i.e. "END", "End_of_section", etc
 */
		if (!_strnicmp (line, "end", 3) || !IsDataLine(line) ||
		   (bossanovaMIME && !strcmp (&line[2], thisBoundary))) // skip '--'
		{
#ifdef DEBUG_DECODE	
			sprintf(debug, "\nSwitching back to skipping\nskipped: %s", line);
			DebugLog();
#endif
			CodingState = DECODE_SKIPPING;

/* If we are switching back to skipping and haven't really received any
 * data yet (< about a line's worth), then the line which gave us the coding type
 * was bogus (regular text line where first char happened to = encoded line 
 * length, Here's one: 'From: marnold@cwis.unomaha.edu (Matthew Eldon Arnold)'
 * Ditch all data so far and the current table  and reprocess
 * this line.  Note if we have a custom table, then we trust it.
 */
			if (thisContentEncoding != CODE_CUSTOM && thisDecode->numBytes < 80)
			{
				thisDecode->numBytes = 0;
				thisContentEncoding = CODE_UNKNOWN;
#ifdef DEBUG_DECODE	
				sprintf(debug, "\nBogus table!  Scrapping so far, reprocessing line: %s", line);
				DebugLog();
#endif
				thisDecode->numLines--;
				return (DecodeLine (thisDecode, line));
			}
			strcpy (prevBlockIdent, thisDecode->ident);
		
       			if (!strncmp (line, "end", 3))
			{
				thisDecode->endFlag = TRUE;
				if (thisNumBlocks == -1 && thisDecode->sequence != -1)
					thisNumBlocks = thisDecode->sequence;
#ifdef DEBUG_DECODE
				sprintf(debug, "\nFound end of file %s", thisDecode->ident);
				DebugLog();
#endif
			}					
		        return (END_BLOCK);
		}
              	return (DecodeDataLine(thisDecode, line));
	}
}

/* ------------------------------------------------------------------------
 *  4 to 3 line decoder
 */
BOOL	DecodeDataLine (TypCoded *thisDecode, char *line)
{
    register unsigned int i, j;
    unsigned int decodedCount, numEncoded, numDecoded, checkSum, startNum, stop;
    unsigned char buf[4], outLine[120];
    char *thisMap = CodingMap[thisContentEncoding];
    
    switch (thisContentEncoding)
    {
    case CODE_QP:
   	// not implemented yet
	return (FAIL);
    
    case CODE_UU:
    case CODE_XX:
    case CODE_CUSTOM:
	// these are 4-to-3 decodings which have the line length encoded
	// as the first char (count is # of chars after decoding)
	checkSum = decodedCount = thisMap[line[0]];
	numEncoded = 4 * ((thisMap[line[0]] + 2) / 3);	// don't include count char
	startNum = 1;
	break;
    case CODE_BASE64:
	// base64 is 4-to-3 decoding with nonexplicit line lengths 
	numEncoded = strlen(line);
	decodedCount = 3 * (numEncoded / 4);
	startNum = 0;
	break;
    }	
    for (i = startNum, numDecoded=0; numDecoded < decodedCount;)
    {
	// Get the next group of four characters 
	// Handle last group of characters in a base64 encoding specially -
	// padding '=' at end means we have fewer than 24 bits after decoding
	// Base64 end scenarios:
	//	'xx==' decodes to 8 bits
	//	'xxx=' decodes to 16 bits
	//	'xxxx' decodes to 24 bits
	//	'x===' can't happen
	stop = min(4, numEncoded - i + 1);
	for (j = 0; j < stop; j++, i++)
	{       
		if (thisContentEncoding == CODE_BASE64 && line[i] == '=')
		{
			buf[j] = 0;
			j--;
			break;
		}
		else
			buf[j] = thisMap[line[i]];
		checkSum += buf[j]; 
	}
	outLine[numDecoded++] = buf[0]<<2|buf[1]>>4;
	if (j == 1) break;
	outLine[numDecoded++] = buf[1]<<4|buf[2]>>2;
	if (j == 2) break;
	outLine[numDecoded++] = buf[2]<<6|buf[3];
    }
    if (numDecoded && AddDataToBlock (currentCoded, outLine, numDecoded) == FAIL)
	return (FAIL);

/*	if (checkSum%64 != line[i])
	{
		printf("\nChecksum error: %d vs. %d", checkSum%64, line[i]);
		break;
	} else
		printf("\nChecksum ok: %c vs. %c", checkSum%64, line[i]);
*/
    return (SUCCESS);
}

/* ------------------------------------------------------------------------
 * AddDataToBlock is called on a line-by-line basis.
 * dataLen should rarely be > 100 bytes
 * The data is type huge, but it's just pointing to a string of chars, so
 * the size does not have to be a power of 2
 */
BOOL
AddDataToBlock (TypCoded *thisDecode, char *newData, unsigned int dataLen)
{
	if (thisDecode->numBytes + (unsigned long) dataLen > thisDecode->maxBytes)
	{ 
	        thisDecode->maxBytes += BASE_BLOCK_SIZE;
#ifdef DEBUG_MEM
		sprintf(debug, "\nReallocing data to %ld", thisDecode->maxBytes);
		DebugLog();
#endif
		if ((thisDecode->data = (char huge *) GlobalReAllocPtr (thisDecode->data, thisDecode->maxBytes, GMEM_MOVEABLE)) == NULL)	
			return (FALSE);
	}
	memmove (thisDecode->data + thisDecode->numBytes, newData, dataLen);
	thisDecode->numBytes += dataLen;
        return (SUCCESS);
}	 
		
/* ------------------------------------------------------------------------
 * 	Determine if the line is a data line
 */
BOOL
IsDataLine (char *line)
{
	if (thisContentEncoding == CODE_UNKNOWN)
	{
/* If this block has same ident as prev block, use same decode table
 * If this block has same ident as any existing threads, use same decode table
 * as matching thread
 * Otherwise, test line for UU, XX, or Base64, returning if find a fit
 */			
		if (currentCoded->ident[0] != '\0' && 
		    !strcmp (currentCoded->ident, prevBlockIdent))
		{
			thisContentEncoding = prevContentEncoding;
#ifdef DEBUG_MAP
			strcpy (debug, "\nUsing same table as prev decode block");
			DebugLog();
#endif
		}
		else
			if ((thisContentEncoding = ThreadTable (customTable, currentCoded->ident)) != CODE_UNKNOWN)
			{
			   if (thisContentEncoding == CODE_CUSTOM)
				CreateCodingMap (CodingMap[CODE_CUSTOM], customTable);
#ifdef DEBUG_MAP
				strcpy (debug, "\nUsing stored thread table");
				DebugLog();
#endif
			}
			else
			{				
				thisContentEncoding = CODE_UU;
				if (TestDataLine (line))
				{
#ifdef DEBUG_MAP
					sprintf (debug, "\nUsing UU table: %s", line);
					DebugLog();
#endif
					return (TRUE);
				}
				thisContentEncoding = CODE_XX;
				if (TestDataLine (line))
				{
#ifdef DEBUG_MAP
					sprintf (debug, "\nUsing XX table: %s", line);
					DebugLog();
#endif
					return (TRUE);
				}
				thisContentEncoding = CODE_BASE64;
				if (TestDataLine (line))
				{
#ifdef DEBUG_MAP
					sprintf (debug, "\nUsing Base64 table: %s", line);
					DebugLog();
#endif
					return (TRUE);
				}
				thisContentEncoding = CODE_UNKNOWN;
				return (FALSE);
			}
	}
	return (TestDataLine(line));
}
		
BOOL
TestDataLine (char *line)
{		
   unsigned int expectedLineLen, dataLen, lineLen;
   register unsigned int i;
   char *thisMap;
   
   thisMap = CodingMap[thisContentEncoding];
   lineLen = strlen(line);
   switch (thisContentEncoding)
   {
   case CODE_UU:
   case CODE_XX:
   case CODE_CUSTOM:
/*
 * The first char of a good data UU/XX/Custom line is the encoded character
 * count for the line.  
 * example M = ascii 77, decodes to 45.  This line will decode to 45 characters,
 * so the # encoded chars should be (int)(4 * ((n+2) / 3)) - (round up and re-encode)
 * So if line[0] is 'M', there should be 60 characters on the line
 * The count does not include the first count char, so the expected line 
 * length is count+1 (i.e. 61). 
 * If the line length is actually 2 + count, then we have a checksum (?)
 */ 
	expectedLineLen = 4 * ((thisMap[line[0]] + 2) / 3) + 1;

	if (lineLen < expectedLineLen)
 		return (FALSE);
/*
 * Count the number of encoded data characters on the line (up to any whitespace)
 * (Some encoders pad with spaces at the end).  Don't forget, a space may be
 * a valid encoded char, so a proper line may very well end with a space.
 * Don't blindly remove all trailing white space!
 */		
 	for (dataLen = lineLen; 
 	     isspace (line[dataLen-1]) && dataLen > 0 && dataLen != expectedLineLen; 
 	     --dataLen);
 	
 	// May have a checksum character, so allow for one extra char
 	if (expectedLineLen != dataLen && expectedLineLen+1 != dataLen)
 		return (FALSE);
 	
 	line[dataLen] = '\0';	// permanently chop off the white space
 	// It's the right length, now check content for match w/ table
	for (i = 0; i < expectedLineLen; i++)
		if (thisMap[line[i]] == UNUSED)
			return (FALSE);
	return (TRUE);

    case CODE_BASE64:
	// permanently remove all trailing space
	while (lineLen > 0 && isspace (line[lineLen-1]))
		line[--lineLen] = '\0';
 	
 	// for base 64, just check if all chars in coding map (allow pad '=')
	for (i = 0; i < lineLen; i++)
		if (thisMap[line[i]] == UNUSED && line[i] != '=')
			return (FALSE);
	return (TRUE);

    case CODE_QP:
	// not implemented yet
    default:
        return (FALSE);
    }        
}                               

/* ------------------------------------------------------------------------
 * 	Mappings for encoding/decoding
 */
int
CreateCodingMap(char *map, char *table)
{
	register unsigned int i;
	
	for (i=0; i < 128; i++)
		map[i] = UNUSED;
	
	// if you see a table[i] character, decode it to i
	// i.e. UU, table[1] is a '!'.  So a '!' should decode to 1
	
	for (i = 0; i < CODINGTABLESIZE; i++)
        	if (map[table[i]] != UNUSED)
        		return (table[i]);	// table[i] is duplicate 
		else        		
			map[table[i]]=i;
	
#ifdef DEBUG_MAP2
	sprintf(debug, "\nCoding Map:\n"); DebugLog();
	for (i=0; i < 128; i++)
	{
		sprintf(debug, "%d ",map[i]);
		DebugLog();
	} 
#endif
	return (-1);			
}

int   
ThreadTable (char *dest, char *ident)
{
	int num;
	register int i;
	
	for (num = -1, i = 0; i < numDecodeThreads; i++)
		if (!_stricmp (ident, threadList[i]->ident))
			num = i;

	if (num == -1)		// no thread by that name 
		return (CODE_UNKNOWN);
	else
	{
		if (threadList[num]->contentEncoding == CODE_CUSTOM)
			strncpy (dest, threadList[num]->customTable, CODINGTABLESIZE);
		return (threadList[num]->contentEncoding);
	}
}
#if 0
void
CreateUUTable()
{
	register unsigned int i;

	for (i=0; i < CODINGTABLESIZE; i++)
		uuTable[i] = i + 32;
	
	uuTable[0] = 96;		/* set space to map to back-quote */	
}
#endif

/* ------------------------------------------------------------------------
 *	Search through a subject: or BEGIN line 
 *	(for a Subject: line,  "Subject:" has already been removed).
 *
 *	Attempt to obtain a block number, and a total # of blocks
 *	Generate an identifier based on the subject, preferably based on the
 *	actual file name.  If guessIdent is TRUE, choose an ident even if
 *	it doesn't look like a fileName.  If guessIdent is FALSE, only fill
 *	ident if we see a fileName (word containing a dot).
 *
 *	Note: at this point, we haven't seen any MIME header, and we're not
 *	really expecting one.  So now is the time to get what we can
 *
 *	Common Subject styles which this function deals with:
 *	filename.ext 1/2 comment
 *	filename.ext(1/2)comment
 *	filename.ext (1/2) comment
 *	filename.ext [1/2] comment
 *	filename.ext 1 of 2 comment
 *	filename.ext part 1 of 2 comment
 *	filename.ext 1 of 2 comment
 *	This is part 1/2 of filename.ext
 *
 *	Also handles the following common BEGIN lines:
 *	BEGIN --- CUT HERE --- Cut Here --- cut here --- abcd.efg n/N
 * 	BEGIN --- CUT HERE --- Cut Here --- cut here --- abcd.efg
 *	BEGIN-------cut here-------CUT HERE-------PART n               // This is TIN
 *	BEGIN -- CUT HERE -- cut here -- abcd.efg part n of N
 *	BEGIN---CUT HERE---BEGIN---CUT HERE---BEGIN PART n/N
 *
 *	For subject lines beginning with the comment, or free text, the
 *	name is harder to guess.  In these cases, prefer a word containing
 *	a dot (filename.ext), or if none, just take the first word.  Scan
 *	any existing threads to see if any of their identifiers matches.
 *	For example,
 *	another encoded file: filename.ext (1/2)
 *	testing encoded files (1/2)	i.e. ident would be testing
 *
 * 	Known to be incorrectly handled by this function:
 *	filename.ext 3.4 (1/2)		i.e. name w/ version number
 *	filename.ext 001		i.e. sequence w/ no # parts
 *	filename.ext1			i.e. part number appended to file name
 */ 
#define SEEK_PARTNUM	1
#define SEEK_NUMPARTS	2
#define IGNORE_NUMBERS	3
void
ParseInfoLine (TypCoded *thisDecode, char *line, BOOL guessIdent)
{
	char tok[MAXINTERNALLINE], next[MAXINTERNALLINE];
	char guessName[MAXINTERNALLINE];
	char *ptr, *thisTok, *tmp;
	int  numberMode;
	
	numberMode = SEEK_PARTNUM;
	ptr = line;
        
	guessName[0] = '\0';
        while (*ptr)
        {
        	if (ReadSubjectToken (thisTok = tok, &ptr) == NULL)
        		break;
        
//	NUMBERS
		if (numberMode != IGNORE_NUMBERS)
		   if (isnumber (thisTok))
		   {
			if (numberMode == SEEK_PARTNUM)
			{
				thisDecode->sequence = atoi (thisTok);
				thisDecode->seqConfidence = LOW;

				if (ReadSubjectToken (next, &ptr) == NULL)
					break;
				if (!_stricmp (next, "/") ||
				    !_stricmp (next, "of"))
				{
					numberMode = SEEK_NUMPARTS;		
					continue;
				}
				else	// no numParts immediately following
					thisTok = next;	// process the next tok
			}
			else	// numberMode == SEEK_NUMPARTS
			{
				thisNumBlocks = atoi (tok);
				numberMode = IGNORE_NUMBERS;
				thisDecode->seqConfidence = HIGH;
        			continue;
			}
		   }
		   else if (numberMode == SEEK_NUMPARTS)
//		   We had '# of non#', a red herring, seek again
			numberMode == SEEK_PARTNUM;
			
//	WORDS
		// Prefer a word containing a dot for the ident (but not a period
		// at the end of a sentence
	        if ((tmp = strchr (thisTok, '.')) && isalpha(*(tmp+1)))
			strcpy (thisDecode->ident, thisTok);
	        else if (guessIdent)
	        {
	        	// skip if already have name w/ confidence
	        	if (thisDecode->ident[0] == '\0')
			{
			// check if any threads have this token as name
        		   if (SearchThreadNames (thisTok))
        			strcpy (thisDecode->ident, thisTok);

			// Save first word found for thread name
			// prefer ident not starting with 're'
	        	   else if (guessName[0] == '\0' || !_strnicmp(guessName, "re", 2))
	        		strcpy (guessName, thisTok);
	        	}
	        }
	}
	if (guessIdent && thisDecode->ident[0] == '\0')
		strcpy (thisDecode->ident, guessName);

#ifdef DEBUG_DECODE
	sprintf(debug, "\nSubject header: name %s, part number %d (confidence %d), num parts %d", 
		thisDecode->ident, thisDecode->sequence, thisDecode->seqConfidence, thisNumBlocks);
	DebugLog();
#endif
}

char *
ReadSubjectToken (char *dest, char **ptr)
{
	int len;
	register int i;
	char *str = *ptr;
	
	if (*str == '\0')
		return ('\0');
		
	len = strcspn (str, " ()[]\\/:,\"'`{};\n\r");

/*	Ignore (skip) all of these delimiters except '/'
 *	Return '/' as a token of length 1
 */                      
	if (len == 0)
		if (*str == '/')
			len = 1;
		else
		{	// skip this delimiter
			(*ptr)++;
			return (ReadSubjectToken (dest, ptr));
		}

        for (i = 0; i < len; i++)
        	dest[i] = tolower(str[i]);
        dest[len] = '\0';

	*ptr = str + len;	// increment line ptr	
}

BOOL
SearchThreadNames (char *name)
{
	register int i;
	int len;
	
	len = strlen (name);

	for (i = 0; i < numDecodeThreads; i++)
		if (!_strnicmp (name, threadList[i]->ident, len))
			return (TRUE);

	return (FALSE);
}

/* ParseAppSpecificLine
 * returns TRUE if it handles the line, else FALSE
 *
 * Example info lines handled by this function:
 *	part=n
 *	file=abc.def
 *	pfile=xyz.abc 
 *	Archive-name: fileident/part0n		// no extension included for filename
 *
 * 	section N of uuencode 5.10 of file abcd.efg   by R.E.M.
 *	section n/N   file abcd.efg   [ Wincode v2.3 ]
 *	[ Section: n/N  File: abcd.efg  Encoder: Wincode v1.4 ]
 *	section n/N abcd.efg  [EnUU 2.1]
 *  abcd.efg    section  n/N   UUXFER ver 2.0 by David M. Read
 *	POST V2.0.0 abcd.efg (Part n/N)
 */
BOOL
ParseAppSpecificLine (TypCoded *thisDecode, char *line)
{
	char *orig, *ptr;
	int seq, num, len;
	float ver;
	char name[MAXINTERNALLINE];
	char copy[MAXINTERNALLINE];
	
   	for (orig = line; *orig && isspace(*orig); orig++);	// skip leading spaces
	
	if (*orig == '\0')        	// blank line (or all spaces)
        	return (SUCCESS);

	len = min(MAXINTERNALLINE-1, strlen(orig));
	strncpy (copy, orig, len);	// make a lower-case copy
	copy[len] = '\0';
	strlwr(copy);
	
	if (!strncmp(orig, "BEGIN", 5))	// specifically comparing case-sensitive
	{
		ParseInfoLine(thisDecode, copy, FALSE);
		return (TRUE);
	}

	if (sscanf(copy, "file=%s", name) == 1)
	{
		strcpy (thisDecode->ident, name);
		return (TRUE);
	}

	if (*copy == 'p')	// info headers starting with 'p'
	{
		if (sscanf(copy, "part=%d", &seq) == 1)
		{
			thisDecode->sequence = seq;
			if (!thisDecode->seqConfidence)
				thisDecode->seqConfidence = LOW; // still don't have number of parts
			return (TRUE);
		}
		if (sscanf(copy, "pfile=%s", name) == 1)
		{
			strcpy (thisDecode->ident, name);
			return (TRUE);
		}
		/* POST */
		if (sscanf (copy, "POST V%*s %s (Part %d/%d)", name, &seq, &num) == 3)
		{
			thisDecode->sequence = seq;
			thisNumBlocks = num;
			thisDecode->seqConfidence = HIGH;
			strcpy (thisDecode->ident, name);
			return (TRUE);
		}
		return (FALSE);	// started with 'p' but we didn't have a template
	}

	/* note some of these look for a version number, then don't use it.  the
	   version number is scanned simply to add to our confidence that this is
	   a valid info line.  we want it to be included in the count - so we know
	   if the line fits the template (hence don't use %*f)
	*/

	/* R.E.M., EnUU, and WinCode v2.x all start with "section " */
	if (!strncmp(copy, "section", 7))
	{
		ptr = &copy[7];
		/* R.E.M. */
		if (sscanf (ptr, " %d of uuencode %f of file %s", &seq, &ver, name) == 3)
		{	
			thisDecode->sequence = seq;
			if (!thisDecode->seqConfidence)
				thisDecode->seqConfidence = LOW; // still don't have number of parts
					
			strcpy (thisDecode->ident, name);
			thisContentEncoding = CODE_UU;
			return (TRUE);
		}
	 	/* EnUU */
	 	if (sscanf (ptr, " %d/%d %s [EnUU %f]", &seq, &num, name, &ver) == 4)
		{
			thisDecode->sequence = seq;
			thisNumBlocks = num;
			thisDecode->seqConfidence = HIGH; 
			strcpy (thisDecode->ident, name);
			thisContentEncoding = CODE_UU;
			return (TRUE);
		}
		/* Wincode v2.x */
		if (sscanf (ptr, " %d/%d  file %s [ Wincode v%f ]", &seq, &num, name, &ver) == 4)
		{
			thisDecode->sequence = seq;
			thisNumBlocks = num;
			thisDecode->seqConfidence = HIGH; 
			strcpy (thisDecode->ident, name);
			return (TRUE);
		}
		return (FALSE);	// started with 'section' but we didn't have a template
	}
	/* Wincode v1.x */
	if (sscanf (copy, "[ section: %d/%d  File: %s  Encoder: Wincode v%f", &seq, &num, &ver) == 4)
	{
		thisDecode->sequence = seq;
		thisNumBlocks = num;
		thisDecode->seqConfidence = HIGH; 
		strcpy (thisDecode->ident, name);
		return (TRUE);
	}

	/* UUXFER */
	if (sscanf (copy, "%s section %d/%d  UUXFER ver %f", name, &seq, &num, &ver) == 4)
	{	
		thisDecode->sequence = seq;
		thisNumBlocks = num;
		thisDecode->seqConfidence = HIGH; 
		strcpy (thisDecode->ident, name);
		thisContentEncoding = CODE_UU;
		return (TRUE);
	}

	if (sscanf(copy, "archive-name: %[^/]/part%d", name, &seq) == 2)		// %[^/] reads a string up to a slash
	{
		strcpy (thisDecode->ident, name);
		thisDecode->sequence = seq;
		if (!thisDecode->seqConfidence)
			thisDecode->seqConfidence = LOW; // still don't have number of parts
		return (TRUE);
	}

	return (FALSE);
}
//-------------------------------------------------------------------------
void
ExecuteDecodedFile(int num, char *fileName)
{
	char ext[4];		// these are DOS specific...
//	char name[9];
	char association[MAXINTERNALLINE];
	char execute[MAXINTERNALLINE+MAXFILENAME];
	int len;
	unsigned int err;
	char *src, *dest, *src2;
	char mybuf[MAXINTERNALLINE];

#ifndef WIN32
	GetFileExtension (ext, fileName);

        if (!_stricmp (ext, "exe"))
        	strcpy (execute, fileName);
	else
	// example association: doc=C:\WIN\WINWORD\winword.exe ^.doc

	if ((len = GetProfileString ("Extensions", (LPCSTR)ext, "", association, MAXINTERNALLINE)) != 0)
	{
	  for (src = association, dest = execute; *src; src++)
	  {
		if (*src == '^')
		{
 		   for (src2 = fileName; *src2 && *src2 != '.'; src2++)
		   	*(dest++) = *src2;
		}
		else
		   *(dest++) = *src;
	  }
	  *dest = '\0';
        }
        else
        {
		sprintf(mybuf, "Cannot execute - no association for file type %s", ext);
		UpdateThreadStatus (num, mybuf);
		return;
	}

#else
        FindExecutable (fileName, ".", association);
	wsprintf (execute, "%s %s", association, fileName);
#endif
	if ((err = WinExec (execute, SW_SHOWNORMAL)) < 32)
	{
		sprintf(mybuf, "Error executing %s, error %u", execute, err);
		UpdateThreadStatus (num, mybuf);
	}
	else if (CodingStatusVerbose)
	{
		sprintf(mybuf, "Executed %s", execute);
		UpdateThreadStatus (num, mybuf);
	}
}
