/***********************************************************************\
Converter class for Convert MacPaint which converts MacPaint files to PostScript files.
Copyright (C) 1993 David John Burrowes

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

The author, David John Burrowes, can be reached at:
	davidjohn@kira.net.netcom.com
	David John Burrowes
	1926 Ivy #10
	San Mateo, CA 94403-1367
\***********************************************************************/


#import "macpaintConverter.h"
#import "File.h"
#import "PSFile.h"
#import <strings.h>
#import <stdio.h>
#import <stdlib.h>		// for malloc and free
#import <sys/param.h>	// for maxpathlen
#include <time.h>

@implementation macpaintConverter

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:	UnpackFrom:To:
//	Parameters:
//		A buffer to unpack data from
//		A buffer to put data into
//	Returns:
//		self
//	Stores:	n/a
//	Description:
//		This takes a buffer in memory, and assumes it contains a macpaint image
//		in packbits form.  This then takes that packed data, and unpacks it into
//		the To: buffer.  Note that it inverts all the image data as it goes, because
//		the mac uses 1 for white, and PS 1 for black (or the reverse or something).
//	Bugs:
//		none, of course.  Actually, there's room for tons.  The most obvious thing is that
//		this does nothing to assure the 'goodness' of the data being passed to it.
//		it just blindly decompresses until it has unpacked 51840 bytes (the size of
//		an unpacked mac paint image)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- UnpackFrom: (ByteString) sourceData To: (ByteString) destData
{
	Byte flag;
	Byte dataByte;
	Integer index;						// counter
	Integer sourceIndex = 0, destIndex = 0;	// counters
	Integer BytesDone = 0;	// Number of bytes put into unpacked buffer
	Integer	nextPeak = 0;

	while (BytesDone < 51840)
	{
		//
		//	Let's not call our manager EVERY byte!.
		//
		if (BytesDone > nextPeak)
		{
			[myManager   SetPercentageDone:  (BytesDone / 518.4) * Weighting];
			nextPeak += 5184;
		}

		//
		//	Read in a byte, which tells us about the following data...
		//	if < 128, we will copy bytes literally, if > 128, will copy the next
		//	byte a numnber of times.
		//
		flag = sourceData[sourceIndex];
		sourceIndex++;
		if (flag < 128)
		{
			//
			// copy one more than the number of Bytes literally.
			//
			flag++;
			for (index = 0; index < flag; index++)
			{
				// invert the data
				destData[destIndex] =  ~sourceData[sourceIndex];
				sourceIndex++;
				destIndex++;
			}
			BytesDone += flag;
		}
		else
		{
			//
			//	copy next Byte 257-flag times
			//
			dataByte = ~sourceData[sourceIndex];
			sourceIndex++;
			for (index = 0; index < 257 -flag; index ++)
			{
				destData[destIndex] = dataByte;
				destIndex++;
			}
			BytesDone += 257-flag;
		}
	}
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-(int) PackDataFrom: (ByteString) source To: (ByteString) dest
{
	Boolean	runs = NO;
	Integer	numTraversedBytes;
	Integer	startByte = 0;
	Integer	lineLength = 72;
	Integer	index = 0;
	Integer	destIndex = 0;
	Integer	ctr;
	Integer	accumulated;
	Integer	runCount;
	

	accumulated = 0;
	do
	{
		startByte = index;
		/* first Byte is always assumed to be OK.  Safe assumption.  Either
			we are starting (Byte 0 should always be good), or we just dumped
			some data, at which index points to the first Byte that did not match
			that pattern, and so is good to use for starting with the next */
		index++;
		accumulated++;
		// Try to accumulate a run
		while ((source[index-1] == source[index]) &&
				((index-startByte) < 128) &&
				(accumulated < lineLength))
		{
			index++;
			accumulated++;
		}
		if ((index-startByte) > 2)
			runs = YES;
		else
		{
			runCount = 1;
			// not enough to justify a run, so carry on. as a litteral sequence of Bytes
			while ((source[index-1] != source[index]) &&
					((index-startByte) < 128) &&
					(accumulated < lineLength))
			{
				index++;
				accumulated++;
			}
//
//	@@ bug: in the above, we should be checking to allow for accumulating
//	@@ runs with length 2, since they are too short to be treated as runs.
//	@@ the present code works, but just isn't ideal.
//
			if (accumulated < lineLength) // We ran into a match, not the end
			{
				index--;	// Thus, back up over 1 of the 2 Bytes.
				accumulated--;
			}
			runs = NO;
		}
		numTraversedBytes = index-startByte;
		if (runs == YES) 
		{
			dest[destIndex] = 257-numTraversedBytes;
			destIndex++;
			dest[destIndex] = source[startByte];
			destIndex++;
		}
		else
		{
			dest[destIndex] = numTraversedBytes - 1;
			destIndex++;
			for (ctr = startByte; ctr < startByte+numTraversedBytes; ctr++)
			{
				dest[destIndex] = source[ctr];
				destIndex++;
			}
		}
	}
	while (accumulated < lineLength);
	return destIndex;	/* return a number that is the num of bytes in the destination */
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:	SetPacking:AndPath:
//	Parameters:
//		a flag indicating if we should pack data that we write out
//		a string indicating where various needed files are to be found
//	Returns: 
//		self
//	Description:
//		This sets up two instance variables, using values we have been provided:
//		- whether output data should be packed or not (yes or no)
//		- Where to find the ps files this object needs to do it's conversion.  This should
//			be a legal path name, or null (indicating the current working directory).
//	Bugs:
//		this does NOT check for the validity of the path.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- SetPacking: (Boolean) shouldPack AndPath: (CString) thepath
{
	if (pathToIncludes != NullCString)
		FreeCString(pathToIncludes);

	pathToIncludes= NewCString(strlen(thepath));
	strcpy(pathToIncludes, thepath);
	
	packing = shouldPack;
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		free
//	Parameters:	none
//	Returns: 	self
//	Description:
//		This frees the current object, which entails merely disposing of the string
//		in the instance variables
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- free
{
	FreeCString(pathToIncludes);
	return [super free];
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		convert:To:
//	Parameters:	a source object  and a destination file object (a PSFile object)
//	Returns:		self
//	Stores:		n/a
//	Description:
//	This method takes the contents of the source file (a MacPaint file, we assume)
//	and changes the contents into an eps file, which is written to the destination file.
//	This relies upon the parameters passed to the init method to determine whether to
//	pack the data
//	This depends on a set of files to provide the PS code for the resulting eps file.
//	Note: When it comes to reporting the percent done, we ignore all the preliminary
//	file reading and writing, and only report on the precentages through the unpacking
//	and packing.  The Weight value is used to aid in reporting accurate results.  When
//	we are not re-packing the data, we set weight to 1.  Otherwise, we set it to 1.
//	One might also ask: Why bother to unpack data we're about to re-pack anyway.
//	the answer is that we need to invert it anyway.  True, we could invert it wihtout
//	going through the effort of unpacking it, but hey, it's easier this way... =)   =(
//	Bugs:
//		Inadequate error responses are provided!
//		Some others I don't know of surely exist.  This object 'grew' too much.  not enough
//		planning
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- Convert: sourceFile To: destFile
{
	PositiveInteger	newPackedSize, imagePackedSize;
	Integer		thetime;
	Instance		headerInfo, prologueCode;
	CString		comment	= NewCString(270);
	CString		tempPath = NewCString(MAXPATHLEN);
	ByteString	unpackedImage, packedImage, newPackedImage;
	Integer		scanlines;
	Integer		packedLoc;
	
	//
	//	Locate 'include' files of PS code we need
	//	open them, and if all goes well, proceed.
	//
	sprintf(tempPath, "%s/%s", pathToIncludes, "PSstuff/EPS_header");
	headerInfo = [[PSFile alloc] initAndUse: tempPath];
	if (packing == YES)
	{
		sprintf(tempPath, "%s/%s", pathToIncludes, "PSstuff/PackedCode");
		prologueCode = [[PSFile alloc] initAndUse: tempPath];
	}
	else
	{
		sprintf(tempPath, "%s/%s", pathToIncludes, "PSstuff/UnpackedCode");
		prologueCode = [[PSFile alloc] initAndUse: tempPath];
	}
	[headerInfo OpenFor: FILE_READ];
	[prologueCode OpenFor: FILE_READ];
	
	if (([headerInfo  GetErrorCode] != 0) || ([prologueCode  GetErrorCode] != 0))
		[self StoreErrorCode: ERR_BADINCLUDES
			AndText: "Could not load a necessary included PS file"];
	else
	{
		//
		//	Write the header and prolog of the destinatin file.
		//
		[destFile AppendFrom: headerInfo];
		thetime = time(NULL);
		sprintf(comment, "CreationDate: (%s", (char*) ctime(&thetime)); 		comment[strlen(comment)-1] = EndOfCString; // ctime puts a \n on which we don't wan
		strcat(comment, ")");
		[destFile WriteDSCComment: comment];
		sprintf(comment, "Title: (%s)", [sourceFile GetFilename]);
		[destFile WriteDSCComment: comment];
		[destFile WriteDSCComment: "EndComments"];
		[destFile WritePSLine: "\nsave\n"];
		[destFile WriteDSCComment: "BeginProlog"];
		//
		[destFile AppendFrom: prologueCode];
		[destFile WriteDSCComment: "EndProlog"];
		[destFile WritePSLine: "\nshowMPimage"];
		//
		//	Write the hex ddata of the image to the output.
		//
		imagePackedSize = [sourceFile FileSize] - 512;
		packedImage = NewByteString(imagePackedSize);
		unpackedImage = NewByteString(52000); // allocate a listtle extra (over 51840)
		newPackedImage = NewByteString(52000); // allocate as much as we'll need
		[sourceFile MoveTo:  512];
		[sourceFile Read: imagePackedSize BytesInto: packedImage];
		if (packing == YES)
			Weighting = 0.5;
		else
			Weighting = 1;
		Weighting = Weighting * .75;
		[self UnpackFrom: packedImage To: unpackedImage];
		if (packing == YES)
		{
			packedLoc	= 0;
			for (scanlines = 0;  scanlines < 720; scanlines++)
			{
				//
				//	Let's not call our manager EVERY scanline.
				//
				if ((scanlines %  72) == 0)
					[myManager   SetPercentageDone: 40+ (((scanlines / 7.2)*.6))];
				newPackedSize = [self PackDataFrom: &(unpackedImage[scanlines*72])
						 To: newPackedImage];
				[destFile Write: newPackedSize BytesOfHexDataFrom: newPackedImage];
			}
			// Add 80> to mark the end of file for an ASCIIHexDecode & Runlength filter 
			[destFile WritePSLine: "80>"];			}
		else
		{
			[myManager   SetPercentageDone:  75];
			for (scanlines = 0;  scanlines < 720; scanlines++) // total 51840 bytes
				[destFile Write: 72 BytesOfHexDataFrom: &(unpackedImage[scanlines*72])];
			[destFile WritePSLine: ">"];	// Add > to mark EOF for ASCIIHexDecode filter 
		}
		//
		//	Finish off the ps code.
		//	93.01.31	djb	Moved EOF after the restore.  Somehow I had it showpage, eof, restore
		[destFile WritePSLine: "showpage"];
		[destFile WritePSLine: "restore"];
		[destFile WriteDSCComment: "EOF"];
		[self StoreErrorCode: ERR_OK AndText: "Simplisitically assumed all went well"];
		FreeByteString(unpackedImage);
		FreeByteString(newPackedImage);
		FreeByteString(packedImage);
	}
	[prologueCode Close];
	[headerInfo Close];
	return self;
}

@end
