/********************************************************************
 *                                                                  *
 *  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       *
 *           EncodeAndSend  - Encodes a file and sends to net       *
 *       EncodeToTextBlock  - Encodes a file into a text block      *
 *            EncodeToFile  - Encodes a file and stores in new 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@planetz.com)                     *
 *   Date: Sept 13, 1993                                            *
 ********************************************************************/

/*
 * $Id: wvcoding.c 1.21 1996/08/31 06:59:57 dumoulin Exp $
 */

#include <windows.h>
#include <windowsx.h>

#define WVCODING

#include "wvglob.h"
#include "winvn.h"
#pragma hdrstop
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>				/* for isspace, isalnum, etc */
#include <shellapi.h>			/* for FindExecutable */

#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
/*
 * Globals for this func
 */

char *codingTable;
char customTable[CODINGTABLESIZE+1];
TypDecodeThread *threadList[MAX_DECODE_THREADS];
int numDecodeThreads;			// number of active decode threads

int currentDecodeThread;
int numDumbDecoded;
int thisNumBlocks;				// MIME vars for current block

char thisContentType[80];
char thisContentDesc[MAXINTERNALLINE];
char thisBoundary[MAXINTERNALLINE], thisBoundaryEnded[MAXINTERNALLINE];
int thisContentEncoding, prevContentEncoding;
char prevBlockIdent[MAXFILENAME];

TypTextBlock *EncodeTextBlock;
HFILE hEncodeFile;

/*
 * Private functions
 */
unsigned long Encode (TypAttachment * thisAttach, unsigned long startByte, unsigned long maxBytes, BOOL (*AddLine) (char *));
unsigned long EncodeLine (unsigned char *outLine, unsigned char *line, int start, int num, int encodingType);
void EncodeUnit (unsigned char *out, unsigned char *in, int num, int encodingType);
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 PreAllocDataByLines (TypCoded * thisDecode);
void ParseInfoLine (TypCoded * thisDecode, char *line, BOOL guessIdent);
BOOL ParseAppSpecificLine (TypCoded * thisDecode, char *line);
BOOL 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 (char *title, int show);
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;
  }
  hSaveCursor = SetCursor (hHourGlass);
  SetCapture (hParentWnd);

  while (1) {
	if ((ptr = fgets (mybuf, MAXINTERNALLINE, DecodeFile)) == NULL)
	  strcpy (mybuf, "~EOF~");	// make sure wrap up occurs ('~' is not in any char map)

	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:;
  SetCursor (hSaveCursor);
  ReleaseCapture ();

  fclose (DecodeFile);
  DecodeDone ();
}

/* ------------------------------------------------------------------------
 * DecodeOpenArticles and DecodeDoc
 * Uses a doc instead of the comm article as a line-provider to the
 * decode routines
 * Assumes DecodeInit is already called, and expects caller to call DecodeDone
 */
void
DecodeOpenArticles (HWND hParentWnd)
{
  register int i;
  // the following two functions are in wvgroup.c
  // extern BOOL article_operation (TypDoc far * Doc, long artindex,
  //	   BOOL (*art_fun) (header_p headers, TypGroup * group, long artindex));
  // extern BOOL selected_false (header_p headers, TypGroup * group, long artindex);

  for (i = 0; i < MAXARTICLEWNDS; i++) {
	if (ArticleDocs[i].InUse && (!CommBusy || CommDoc != &ArticleDocs[i])) {
	  // ensure the article is deselected now
	  if (ArticleDocs[i].ParentDoc) {
		article_operation (ArticleDocs[i].ParentDoc, ArticleDocs[i].LastSeenLineID, selected_false);
		InvalidateRect (ArticleDocs[i].ParentDoc->hDocWnd, NULL, FALSE);
	  }
	  DecodeDoc (hParentWnd, &ArticleDocs[i]);
	}
  }
}

void
DecodeDoc (HWND hParentWnd, TypDoc * Document)
{
  int result;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;

  if ((currentCoded = InitCoded (hParentWnd)) == NULL) {
	MessageBox (hParentWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
	return;
  }

  hSaveCursor = SetCursor (hHourGlass);
  SetCapture (hParentWnd);
  LockLine (Document->hFirstBlock, sizeof (TypBlock), (TypLineID) 0L, &BlockPtr, &LinePtr);
  while (LinePtr->length != END_OF_BLOCK) {
	result = DecodeLine (currentCoded,GetTextPtr(LinePtr));

	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);
		goto abortDecodeDoc;
	  }
	}
	NextLine (&BlockPtr, &LinePtr);
  }
  if (currentCoded != NULL)		// finish final block

	CompleteThisDecode ();

abortDecodeDoc:;
  SetCursor (hSaveCursor);
  ReleaseCapture ();
  GlobalUnlock (BlockPtr->hCurBlock);
}
/* ------------------------------------------------------------------------
 *    Encode a file and ask the user for a file to name to store it in
 */
void
EncodeToFile (HWND hParentWnd, TypAttachment * thisAttach)
{
  char fileName[MAXFILENAME];
  HFILE hFile;
  OFSTRUCT ofs;

  fileName[0] = '\0';
  if (AskForNewFileName (hParentWnd, fileName, "", FALSE) != FAIL) {
	if ((hFile = OpenFile ((char far *) fileName, &ofs, OF_CREATE)) < 0) {
	  MessageBox (hParentWnd, "Unable to open output file", "File Error", MB_OK | MB_ICONSTOP);
	}
	else {
	  CreateStatusArea (hParentWnd, "Encoding To File", SW_SHOWNORMAL);
	  hSaveCursor = SetCursor (hHourGlass);
	  SetCapture (hParentWnd);
	  strcpy (currentCoded->ident, thisAttach->fileName);	// status window info

	  if (EncodeIntoFile (hFile, thisAttach, 0, 0) == 0) {
		MessageBox (hParentWnd, "Encode to file failed", "Encode Error", MB_OK | MB_ICONSTOP);
	  }
	  SetCursor (hSaveCursor);
	  ReleaseCapture ();
	  DestroyStatusArea ();
	}
  }
  _lclose (hFile);
}

/* ------------------------------------------------------------------------
 *    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, SendComposition, etc
 *  to show status in progress
 *  Use UpdateBlockStatus to update/display status
 *  Use DestroyStatusArea to clean up when done
 */
void
CreateStatusArea (HWND hParentWnd, char *title, int show)
{
  if ((currentCoded = InitCoded (hParentWnd)) == NULL)
	return;
  CreateCodingStatusWnd (title, show);
  currentCoded->sequence = 1;	// status window info
}

void
DestroyStatusArea ()
{
  if (currentCoded) {
	DestroyCodedBlock (&currentCoded);
  }
  if (hCodedBlockWnd) {
	DestroyWindow (hCodedBlockWnd);
  }
  hCodedBlockWnd = (HWND) NULL;
}

void
CreateCodingStatusWnd (char *title, int show)
{
  char temp[MAXINTERNALLINE];
  int x, y;

  /* Create coding status window, default top center */
  x = (xScreen - STATUSWIDTH) >> 1;
  y = 1;

  /* If the screen position has been saved, use that instead. */
  GetPrivateProfileString (CODING, "BlockStatusWindowPos", "!",
						 temp, MAXINTERNALLINE, szAppProFile);
  if (*temp != '!') {
	sscanf (temp, "%d,%d", &x, &y);
  }

  hCodedBlockWnd = CreateWindowEx (WS_EX_DLGMODALFRAME, "WinVnBlockCoding", title,
				   WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_THICKFRAME,
				   x, y, STATUSWIDTH, STATUSHEIGHT,
					   (HWND) NULL, (HMENU) NULL, hInst, (void far *) NULL);

  SetHandleBkBrush (hCodedBlockWnd, hStatusBackgroundBrush);
  ShowWindow (hCodedBlockWnd, show);
}


/* ------------------------------------------------------------------------
 *    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 ()
{
  if (hCodedBlockWnd) {
	InvalidateRect (hCodedBlockWnd, NULL, FALSE);
	UpdateWindow (hCodedBlockWnd);
  }
}

/* ------------------------------------------------------------------------
 *    Encode a file to file, net, or textblock
 *  Assumes currentCoded is initialized
 *    startByte is file byte to start at (0 == beginning)
 *    maxBytes is # encoded bytes to generate before stopping (0 == no restriction)
 *    AddLine is a pointer to a function which will be used to deal with each
 *      encoded line appropriately (i.e. add to file, send to net...)
 * returns # bytes read from file or 0 on failure
 */
BOOL
EncodeIntoFileHelper (char *str)
{
  return (_lwrite (hEncodeFile, str, lstrlen (str)) == HFILE_ERROR) ? FAIL : SUCCESS;
}

unsigned long
EncodeIntoFile (HFILE hFile, TypAttachment * thisAttach,
				unsigned long startByte, unsigned long maxBytes)
{
  unsigned long result;
  hEncodeFile = hFile;
  CodingState = ATTACH_PROCESSING;
  result = Encode (thisAttach, startByte, maxBytes, (BOOL (*)(char *)) EncodeIntoFileHelper);
  CodingState = INACTIVE;
  return result;
}

unsigned long		// assumes appropriate sockets are opened and initialized
EncodeAndSend (TypAttachment * thisAttach, unsigned long startByte,
			   unsigned long maxBytes)
{
  unsigned long result;
  int oldState;
//  extern BOOL SendOneLine (char *str);	// in wvattach.c

  oldState = CodingState;
  CodingState = ATTACH_SENDING;
  UpdateBlockStatus ();
  result = Encode (thisAttach, startByte, maxBytes, SendOneLine);
  CodingState = oldState;
  UpdateBlockStatus ();
  return result;
}


#if 0							// No one is using this right now
BOOL
EncodeToTextBlockHelper (char *str)
{
  return AddLineToTextBlock (EncodeTextBlock, str);
}

unsigned long
EncodeToTextBlock (TypTextBlock * textBlock, TypAttachment * thisAttach,
				   unsigned long startByte, unsigned long maxBytes)
{
  int result;
  EncodeTextBlock = textBlock;
  CodingState = ATTACH_PROCESSING;
  result = Encode (thisAttach, startByte, maxBytes, (BOOL (*)(char *) EncodeToTextBlockHelper));
  CodingState = INACTIVE;
  return result;
}
#endif

unsigned long
Encode (TypAttachment * thisAttach, unsigned long startByte,
		unsigned long maxBytes, BOOL (*AddLine) (char *))
{
  HFILE hFile;
  int numRead, lineLen, start;
  register int i;
  unsigned char inBuf[ENCODE_LINE_LEN];
  unsigned char outLine[MAXINTERNALLINE];
  unsigned long totalNumAdded, totalNumRead;
  BOOL done;
  int len, encodingType;
  extern char *NameWithoutPath (char *tempName, char *fileName);

  totalNumAdded = totalNumRead = 0;
  encodingType = EncodingTypeToNum (thisAttach->encodingType);

  // Set up encoding map
  switch (encodingType) {
  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 ((*AddLine) ("table\r\n"))
	  return (0);
	memmove (str, UserCodingTable, 32);
	str[32] = '\r';
	str[33] = '\n';
	str[34] = '\0';
	if ((*AddLine) (str))
	  return (0);
	memmove (str, &UserCodingTable[32], 32);
	if ((*AddLine) (str))
	  return (0);

	totalNumAdded += 75;		// table + \r\n + 32 + \r\n + 32 + \r\n

	currentCoded->numBytes += 75;
	currentCoded->numLines += 3;
	break;
  }

  // Prepare for encoding
  switch (encodingType) {
  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
	if (startByte == 0) {
	  NameWithoutPath (str, thisAttach->fileName);
	  len = _snprintf ((char*)outLine, MAXINTERNALLINE, "begin 755 %s\r\n", str);
	  if ((*AddLine) ((char*)outLine))
		return (0);

	  totalNumAdded += len;
	  currentCoded->numBytes += len;
	  currentCoded->numLines++;
	}
	// set 1st char to appropriate line length value
	outLine[0] = codingTable[ENCODE_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 = ENCODE_LINE_LEN;

  // open the file and have at it
  if ((hFile = _lopen (thisAttach->fileName, LOPEN_READ)) == HFILE_ERROR) {
	return (0);
  }
  if (startByte && _llseek (hFile, startByte, 0) == HFILE_ERROR) {
	_lclose (hFile);
	return (0);
  }
  done = FALSE;
  while (!done && (!maxBytes || totalNumAdded < maxBytes)) {
	if ((numRead = _lread (hFile, inBuf, lineLen)) == HFILE_ERROR) {
	  _lclose (hFile);
	  return (0);
	}
	if (numRead < lineLen) {	// change last line length value

	  if (encodingType != CODE_BASE64) {
		outLine[0] = codingTable[numRead];
	  }
	  for (i = numRead; i < lineLen; i++) {
		inBuf[i] = 0;
	  }
	  done = TRUE;
	}
	EncodeLine (outLine, inBuf, start, numRead, encodingType);
	strcat ((char*)outLine, "\r\n");
	if ((*AddLine) ((char*)outLine)) {
	  _lclose (hFile);
	  return (0);
	}
	totalNumAdded += strlen ((char*)outLine);
	currentCoded->numLines++;
	currentCoded->numBytes += numRead;
	totalNumRead += numRead;

	if (currentCoded->numLines % STATUS_UPDATE_FREQ == 0)
	  UpdateBlockStatus ();
  }
  _lclose (hFile);

  if (done) {
	switch (encodingType) {
	case CODE_UU:
	case CODE_XX:
	case CODE_CUSTOM:
	  // add zero length line
	  outLine[0] = codingTable[0];
	  outLine[1] = '\r';
	  outLine[2] = '\n';
	  outLine[3] = '\0';
	  if ((*AddLine) ((char*)outLine))
		return (0);
	  if ((*AddLine) ("end\r\n"))
		return (0);
	  currentCoded->numLines += 2;
	}
  }
  UpdateBlockStatus ();

  return (totalNumRead);
}

/* ------------------------------------------------------------------------
 *    Encode num chars from line
 *  Put data in outLine starting at start
 */
unsigned long
EncodeLine (unsigned char *outLine, unsigned char *line, int start, int num, int encodingType)
{
  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, encodingType);

  outLine[j] = '\0';
  return (j);
}

void
EncodeUnit (unsigned char *out, unsigned char *in, int num, int encodingType)
{
  out[0] = codingTable[((in[0] >> 2) & 63)];
  out[1] = codingTable[(((in[0] << 4) | (in[1] >> 4)) & 63)];
  if (num == 1) {
	if (encodingType == CODE_BASE64) {
	  strcpy ((char*)&out[2], "==");
	}
	else {
	  out[2] = 0;
	}
	return;
  }

  out[2] = codingTable[(((in[1] << 2) | (in[2] >> 6)) & 63)];
  if (num == 2) {
	if (encodingType == CODE_BASE64) {
	  strcpy ((char*)&out[3], "=");
	}
	else {
	  out[3] = 0;
	}
	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 ("Decoding Status",
				MinimizeStatusWindows ? SW_SHOWMINNOACTIVE : SW_SHOWNORMAL);
  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];
  int i;
#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')
		_snprintf (name, MAXINTERNALLINE, "%s   ", threadList[0]->name);
	  else
		_snprintf (name, MAXINTERNALLINE, "%s   ", threadList[0]->ident);
	}

	if (threadList[0]->numBlocks == 0) {	// premature end of file

	  WriteDecodeThread (0);
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "%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) {
	  _snprintf (mybuf, MAXINTERNALLINE, "%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);
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "%sDecode is missing parts, written total size %ld", name, threadList[0]->totalBytes);
	  UpdateThreadStatus (0, mybuf);
	}
	else {
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "%sCompleteness confidence is medium, written total size %ld", name, threadList[0]->totalBytes);
	  UpdateThreadStatus (0, mybuf);
	  WriteDecodeThread (0);
	}
	DestroyThread (0);			// decrements numDecodeThreads

  }
  for (i = 0; i < NumStatusTexts; i++)
	CodingStatusText[i]->IsBusy = FALSE;

  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->estNumLines = 0;
  thisCoded->name[0] = '\0';
  thisCoded->ident[0] = '\0';
  thisCoded->endFlag = thisCoded->beginFlag = FALSE;
  thisCoded->hParentWnd = hParentWnd;

  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;

	  width = xScreen >> 1;
	  height = (yScreen >> 1) - CaptionHeight;
	  maxX = 3 * (width >> 1);	/* 3/4 screen width  */
	  maxY = 3 * (height >> 1);	/* 3/4 screen height */

	  x = 1;
	  y = yScreen >> 2;

	  /* If the screen position has been saved, use that instead. */
	  GetPrivateProfileString (CODING, "DecodeStatusWindowPos", "!",
							 mybuf, MAXINTERNALLINE, szAppProFile);
	  if (*mybuf != '!') {
		sscanf (mybuf, "%d,%d,%d,%d", &x, &y, &width, &height);
	  }

	  if (CodingStatusVerbose)
		_snprintf (mybuf, MAXINTERNALLINE, "Decoding Status for file %s", (threadList[num]->name[0] != '\0') ? threadList[num]->name : threadList[num]->ident);
	  else
		strcpy (mybuf, "Decoding Status");

	  threadList[num]->statusText->hTextWnd =
		CreateWindow ("WinVnCoding", mybuf,
					  WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
					  (x + (NumStatusTexts * StatusCharWidth)) % maxX,
					  (y + (NumStatusTexts * StatusLineHeight)) % maxY,
					  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,
				MinimizeStatusWindows ? SW_SHOWMINNOACTIVE : SW_SHOWNORMAL);
	  UpdateWindow (threadList[num]->statusText->hTextWnd);
	}
	else
	  threadList[num]->statusText = CodingStatusText[0];


	threadList[num]->statusText->IsBusy = TRUE;

  }

/* 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) {
	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
  CodingState = DECODE_SKIPPING;

  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

	  num = currentDecodeThread;
	  if (CodingStatusVerbose)
		_snprintf (mybuf, MAXINTERNALLINE,
				   "Decode missing end, total size %ld, wrote to file %s", threadList[num]->totalBytes, threadList[num]->dosFileName);
	  else
		_snprintf (mybuf, MAXINTERNALLINE,
				"%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 = TRUE;
	strcpy (currentCoded->name, currentCoded->ident);
  }

  if (AddToThreadList () == FAIL)
	return (FAIL);

  num = currentDecodeThread;

  if (threadList[num]->contentEncoding == CODE_BASE64 &&
	  threadList[num]->expectedNumBlocks != 0 &&
	  currentCoded->sequence == threadList[num]->expectedNumBlocks) {
	/* base-64 end flag if last block */
	currentCoded->endFlag = TRUE;
  }

  prevContentEncoding = thisContentEncoding;
  thisContentEncoding = CODE_UNKNOWN;

  singleBlockDone = currentCoded->beginFlag && currentCoded->endFlag;

  if (!CodingStatusVerbose &&
	  (threadList[num]->numBlocksWritten == 0 && threadList[num]->numBlocks == 1)) {
	_snprintf (mybuf, MAXINTERNALLINE,
			   "%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)
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "Decode complete, total size %ld, wrote to file %s", threadList[num]->totalBytes, threadList[num]->dosFileName);
	else
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "%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) {
	_snprintf (mybuf, MAXINTERNALLINE,
			   "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) {
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "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';
	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')
	  return (FAIL);

	strcpy (actualFileName, DecodePathName);
	if (actualFileName[strlen (actualFileName) - 1] != '\\')
	  strcat (actualFileName, "\\");
	strcat (actualFileName, threadList[num]->dosFileName);

	if (SmartFile (hCodedBlockWnd, actualFileName) == FAIL)
	  return (FAIL);
  }
  strcpy (threadList[num]->dosFileName, actualFileName);

  if ((hWriteFile = OpenFile ((char far *) actualFileName, &outFileStruct, fileMode)) < 0) {
	/* last chance to fix... smart filer didn't get a good name... */
	if (AskForNewFileName (hCodedBlockWnd, actualFileName, NULL, FALSE) == FAIL) {
		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
 */
BOOL
DecodeLine (TypCoded * thisDecode, char *line)
{
  register int i;
  static int table_count;
  char thisBlockName[MAXINTERNALLINE], *ptr;
  int mode;

  /* ignore blank lines */
  if (*line == '\0' || (*line == '\n' && *(line + 1) == '\0') ||
	  (*line == '\r' && *(line + 1) == '\n' && *(line + 2) == '\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') {
		NameWithoutPath (thisDecode->name, strlwr (thisBlockName));
	  }
	  else {
		NameWithoutPath (thisDecode->name, thisDecode->ident);
	  }

	  thisDecode->sequence = 1;
	  thisDecode->beginFlag = TRUE;
//          thisDecode->mode = mode;

	  PreAllocDataByLines (thisDecode);
	  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 (!UsingMIME && !_strnicmp (line, "MIME-Version:", 13))
	  UsingMIME = TRUE;
	else if (!DumbDecode && ParseAppSpecificLine (thisDecode, line))
	  return (SUCCESS);
	else if (!DumbDecode && !_strnicmp (line, "lines:", 6)) {
	  for (ptr = &line[6]; isspace (*ptr); ptr++);	/* skip spaces */
	  thisDecode->estNumLines = atol (ptr);
	  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)) {
	  // found good line, process it and continue processing
#ifdef DEBUG_DECODE
	  sprintf (debug, "\nFound start of next data section");
	  DebugLog ();
#endif
	  PreAllocDataByLines (thisDecode);
	  CodingState = DECODE_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, CODINGTABLESIZE);
	  str[CODINGTABLESIZE] = '\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 ((thisContentEncoding != CODE_BASE64 && !_strnicmp (line, "end", 3)) || 
    !IsDataLine (line) ||
		(UsingMIME && *thisBoundary &&
		 (!strcmp (&line[2], thisBoundary) || !strcmp (&line[2], thisBoundaryEnded)))) {
#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));
  }
  DEBUG_BREAK; // verify this as proper return value...
  return FAIL;
}

/* ------------------------------------------------------------------------
 *  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];
  int offset;
  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[(unsigned char)line[0]];
	// to precisely determine the numEncoded from this count char,
	// add (4 * the number of full 3-byte units) and a remainder offset
	// the remainder offset is 2 if 1 byte over, or 3 if 2 bytes over
	if ((offset = (decodedCount % 3)) != 0) {
	  offset++;
	}
	numEncoded = 4 * (decodedCount / 3) + offset;
	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
	if ((stop = numEncoded - i + 1) < 4) {
	  memset (buf, 0, 4);
	}
	else {
	  stop = 4;
	}
	for (j = 0; j < stop; j++, i++) {
	  if (thisContentEncoding == CODE_BASE64 && line[i] == '=') {
		buf[j] = 0;
		j--;
		break;
	  }
	  else {
		buf[j] = thisMap[(unsigned char)line[i]];
	  }
//          checkSum += buf[j];
	}
	outLine[numDecoded++] = buf[0] << 2 | buf[1] >> 4;
	if (j == 1 || numDecoded == decodedCount)
	  break;
	outLine[numDecoded++] = buf[1] << 4 | buf[2] >> 2;
	if (j == 2 || numDecoded == decodedCount)
	  break;
	outLine[numDecoded++] = buf[2] << 6 | buf[3];
  }
  if (numDecoded && AddDataToBlock (thisDecode, (char*)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);
}

void
PreAllocDataByLines (TypCoded * thisDecode)
{
  unsigned long newSize;
  char huge *resizer;

  if (currentCoded->estNumLines) {
	/* pre-alloc data to rough estimate of # bytes for decode.  allocing
	 * up front saves time - reallocs are slow.  If this fails, allow
	 * to continue - the # lines may have been wrong...
	 */
	newSize = thisDecode->estNumLines * 45L;
	if ((resizer = (char huge *) GlobalReAllocPtr (thisDecode->data,
										 newSize, GMEM_MOVEABLE)) != NULL) {
	  thisDecode->data = resizer;
	  thisDecode->maxBytes = newSize;
#ifdef DEBUG_MEM
	  sprintf (debug, "\nRealloced data to %ld based on lines header %ld",
			   thisDecode->maxBytes, thisDecode->estNumLines);
	  DebugLog ();
#endif
	}
  }
}

/* ------------------------------------------------------------------------
 *    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;
  unsigned int decodedCount, offset, tolerance;
  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;

	decodedCount = thisMap[(unsigned char) line[0]];
	/* to precisely determine the numEncoded from this count char,
	 * add (4 * the number of full 3-byte units) and a remainder offset
	 * the remainder offset is 2 if 1 byte over, or 3 if 2 bytes over.
	 * Not all encoders are smart enough to do this though, and just leave
	 * the last (4-offset) bytes as junk (usually bytes found in the same
	 * location on the previous line).  So allow a tolerance of (4-offset)
	 * bytes
	 */
	if ((offset = (decodedCount % 3)) != 0) {
	  offset++;
	  tolerance = 4 - offset;
	}
	else {
	  tolerance = 0;
	}
	expectedLineLen = 4 * (decodedCount / 3) + offset + 1;	// add 1 for start char

	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!
 */
	// first strip trailing non-spaces (i.e. \n\r)
	for (dataLen = lineLen;
		 isspace (line[dataLen - 1]) && (thisMap[' '] == UNUSED || line[dataLen - 1] != ' ') && dataLen > 0;
		 --dataLen);

	// now deal with spaces
	if (thisMap[' '] != UNUSED) {
	  for (; line[dataLen - 1] == ' ' && dataLen > 0 && dataLen != expectedLineLen;
		   --dataLen);
	}
	// May have a checksum character, so allow for one extra char
	// also allow for encoders which include the count char in the line count
	if (expectedLineLen != dataLen && expectedLineLen + tolerance != dataLen &&
		expectedLineLen + 1 != dataLen && expectedLineLen + tolerance + 1 != dataLen &&
		expectedLineLen - 1 != dataLen && expectedLineLen + tolerance - 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 < dataLen; i++)
	  if (thisMap[(unsigned char)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[(unsigned char)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 < 256; 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 < 256; 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))
	  break;

//  NUMBERS
	if (numberMode != IGNORE_NUMBERS)
	  if (isnumber (thisTok)) {
		if (numberMode == SEEK_PARTNUM) {
		  thisDecode->sequence = atoi (thisTok);
		  thisDecode->seqConfidence = LOW;

		  if (!ReadSubjectToken (next, &ptr))
			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)) && strlen (thisTok) > 1)
		  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
}

BOOL
ReadSubjectToken (char *dest, char **ptr)
{
  int len;
  register int i;
  char *str = *ptr;

  if (*str == '\0')
	return FALSE;

  len = strcspn (str, " ()[]\\/:,\"'`<>{};\t\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

  return TRUE;
}

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);
}

/* ------------------------------------------------------------------------
 * simple MIME header handling - not very smart!
 */
BOOL
SkipSpace (char **ptr)
{
  while (**ptr && isspace (**ptr)) {
	(*ptr)++;
  }

  return (**ptr != '\0');
}


BOOL
SkipToNextClause (char **ptr)
{
  while (**ptr && **ptr != ';') {	// skip to semi-colon or end

	(*ptr)++;
  }
  if (**ptr == ';') {			// skip semi-colon

	(*ptr)++;
  }
  if (!SkipSpace (ptr)) {		// skip space after semi-colon

	return FALSE;
  }
  return TRUE;
}

BOOL
GetNumber (int *dest, char *src)
{
  char temp[20];
  char *ptr, *destPtr;

  ptr = src;
  destPtr = temp;
  *destPtr = '\0';
  while (*ptr && isdigit (*ptr)) {
	*destPtr++ = *ptr++;
  }
  if (*temp == '\0')
	return FALSE;

  *dest = atoi (temp);
  return TRUE;
}

BOOL
GetPossiblyQuotedStr (char *dest, char *src, int maxLen)
{
  char *ptr, *destPtr;
  int len;
  BOOL quoted = FALSE;

  ptr = src;
  destPtr = dest;
  *destPtr = '\0';
  len = 0;
  if (*ptr == '"' || *ptr == '\'') {
	ptr++;
	quoted = TRUE;
  }

  // if quoted, seek to end quote (allow spaces and semicolons inside quotes)
  // if not in quotes, read up to space or semicolon
  while (*ptr && len < maxLen) {
	if (quoted) {
	  if (*ptr == '"' || *ptr == '\'') {
		break;
	  }
	}
	else {
	  if (isspace (*ptr) || *ptr == ';') {
		break;
	  }
	}
	len++;
	*destPtr++ = *ptr++;
  }
  *destPtr = '\0';

  if (*dest == '\0')
	return FALSE;

//  if (quoted && *ptr != '"' && *ptr != '\'')  // missing end-quot
  //      return FALSE;

  return TRUE;
}

/* ParseMIMEHeader
 * this a very simplisitc approach
 * returns TRUE if it was a MIME header (regardless of whether we handled it ok)
 */
BOOL
ParseMIMEHeader (TypCoded * thisDecode, char *line)
{
  char *ptr;
  int result, num;
  char temp[MAXINTERNALLINE];

  ptr = line;
  result = FALSE;

  if (thisContentEncoding == CODE_UNKNOWN &&
	  !_strnicmp (ptr, "Content-Transfer-Encoding:", 26)) {
	ptr += 26;
	if (SkipSpace (&ptr) &&
		GetPossiblyQuotedStr (temp, ptr, MAXINTERNALLINE - 1)) {
	  if (!_stricmp (temp, "base64")) {
		thisContentEncoding = CODE_BASE64;
		thisDecode->sequence = 1;
	  }
	  else if (!_strnicmp (temp, "x-uu", 4)) {
		thisContentEncoding = CODE_UU;
	  }
	  else if (!_strnicmp (temp, "x-xx", 4)) {
		thisContentEncoding = CODE_XX;
	  }
	}
	return TRUE;
  }
  while (*ptr) {
	if (!_strnicmp (ptr, "name=", 5)) {
	  if (GetPossiblyQuotedStr (thisDecode->ident, &ptr[5], MAXINTERNALLINE - 1)) {
		result = TRUE;
	  }
	}
	if (!_strnicmp (ptr, "number=", 7)) {
	  if (GetNumber (&(thisDecode->sequence), &ptr[7])) {
		result = TRUE;
		thisDecode->seqConfidence = HIGH;
	  }
	}
	if (!_strnicmp (ptr, "total=", 6)) {
	  if (GetNumber (&num, &ptr[6])) {
		result = TRUE;
		thisNumBlocks = num;
		thisDecode->seqConfidence = HIGH;
	  }
	}
	if (!_strnicmp (ptr, "boundary=", 9)) {
	  /* this is no good for encapsulated multipart sections. but ok for now */
	  if (GetPossiblyQuotedStr (thisBoundary, &ptr[9], MAXINTERNALLINE - 1)) {
		_snprintf (thisBoundaryEnded, MAXINTERNALLINE, "%s--", thisBoundary);
		result = TRUE;
	  }
	}
	if (!SkipToNextClause (&ptr)) {
	  break;
	}
  }
  return result;
}
#ifdef BEGINNINGS_OF_A_BETTER_MIME_APPROACH
if (!strncmp (line, "Content-Type:", 13)) {
  ptr = &copy[13];
  if (!SkipSpace (&ptr)) {
	return TRUE;
  }
  if (!strncmp (ptr, "application/octet-stream", 25)) {
	ptr += 25;
	while (*ptr) {
	  if (!SkipToNextClause (&ptr)) {
		return TRUE;
	  }
	  if (GetNameStr (ptr, name)) {
		return TRUE;
	  }
	}
  }
}
if (GetNameStr (ptr, name)) {
  strcpy (thisDecode->ident, name);
  return TRUE;
}
#endif

/* ------------------------------------------------------------------------
 * 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)
 *  Interim MIME support
 *  Content-Type: application/octet-stream; [tokens]; name="abcd.efg"; [tokens]
 *  name=abcd.efg
 */
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));
  strntcpy (copy, orig, len);	// make a lower-case copy

  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)
   */

  if (UsingMIME && ParseMIMEHeader (thisDecode, orig)) {
	return TRUE;
  }

  /* 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, name, &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 association[MAXINTERNALLINE];
  char execute[MAXINTERNALLINE + MAXFILENAME];
  char ext[MAXFILENAME];
  unsigned int err;
#ifdef WIN32
  STARTUPINFO sInfo;
  PROCESS_INFORMATION pInfo;
#endif

  if (FindExecutable (fileName, ".", association) <= (HINSTANCE) 32) {
	_snprintf (str, MAXINTERNALLINE,
			   "Cannot execute - no association for file type %s", GetFileExtension (ext, fileName));
	UpdateThreadStatus (num, str);
	return;
  }

  _snprintf (execute, MAXINTERNALLINE + MAXFILENAME, "%s %s", association, fileName);
#ifdef WIN32
  memset (&sInfo, 0, sizeof (STARTUPINFO));
  sInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
  sInfo.wShowWindow = SW_SHOWNORMAL;
  err = CreateProcess (NULL, execute, NULL, NULL, FALSE,
					   DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, NULL, NULL,
					   &sInfo, &pInfo);
  if (err == TRUE) {			// successful

	CloseHandle (pInfo.hThread);	/* let it run detached */
	CloseHandle (pInfo.hProcess);
  }
  else {
#else
  if ((err = WinExec (execute, SW_SHOWNORMAL)) < 32) {
#endif
	_snprintf (str, MAXINTERNALLINE, "Error executing %s, error %u", execute, err);
	UpdateThreadStatus (num, str);
	return;
  }

  if (CodingStatusVerbose) {
	_snprintf (str, MAXINTERNALLINE, "Executed %s", execute);
	UpdateThreadStatus (num, str);
  }
}

/* this is for emacs
 * Local variables:
 * tab-width: 2
 * end:
 */
