/* -*- Mode: C++; tab-width: 4; tabs-indent-mode: nil -*-
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
#include "su_pas.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

OSErr PAS_EncodeFile(FSSpec *inSpec, FSSpec *outSpec);
OSErr PAS_DecodeFile(FSSpec *inSpec, FSSpec *outSpec);

OSErr PAS_encodeResource(FSSpec *inFile, short outRefNum);
OSErr PAS_decodeResource(PASEntry *entry, FSSpec *outFile, short inRefNum);

OSErr PAS_encodeMisc(FSSpec *inFile, short outRefNum);
OSErr PAS_decodeMisc(PASEntry *entry, FSSpec *outFile, short inRefNum);

OSErr PAS_encodeData(FSSpec *inFile, short outRefNum);
OSErr PAS_decodeData(PASEntry *entry, FSSpec *outFile, short inRefNum);

OSErr PAS_encodeHeader(short refnum);
OSErr PAS_decodeHeader(short refNum, PASHeader *header);


unsigned long PAS_getDataSize(FSSpec *spec);
short PAS_getResourceID(Handle resource);

OSErr PAS_flattenResource(ResType type, short *ids, long count, short source, short dest);
OSErr PAS_unflattenResource(PASResource *pasRes, Ptr buffer);

void PAS_sortTypes(short sourceRefNum, ResType **resTypePtr, long *count);
void PAS_sortIDs(short sourceRefNum, OSType theType, short **IdPtr, long *count);
void PAS_bubbleSortResType(ResType *types, long count);
void PAS_bubbleSortIDS(short *ids, long count);


OSErr PAS_EncodeFile(FSSpec *inSpec, FSSpec *outSpec)
{
	OSErr		err;
	short		outRefNum;
	
	PASEntry 	dataEntry, miscEntry, resourceEntry;
	long		sizeOfEntry;


	if (inSpec == NULL || outSpec == NULL)
		return paramErr;
		

	memset(&dataEntry, 0, sizeof(PASEntry));
	memset(&miscEntry, 0, sizeof(PASEntry));
	memset(&resourceEntry, 0, sizeof(PASEntry));
	
	FSpDelete( outSpec ) ;
	
	err = FSpCreate( outSpec, kCreator, kType ,smSystemScript );
	
	if (err != noErr) 	return err;
	
	
	err = FSpOpenDF(outSpec, fsRdWrPerm, &outRefNum);
	
	if (err != noErr)  goto error;
	

	/* Write Out Header */
		
	err = PAS_encodeHeader(outRefNum);
	if (err != noErr)  goto error;
	
	/* Why am I using three (3)?
		
		E stand for entry.
		
	   The data for the entry is after the THREE headers 
	
		|---------|----|----|----|---------------------->
		   header    E    E    E
	
	*/
	
	
	/* Write Out Data Entry */
	dataEntry.entryID		=	ePas_Data;
	dataEntry.entryLength	=	PAS_getDataSize(inSpec);
	dataEntry.entryOffset	=	sizeof(PASHeader) + (3 * sizeof(PASEntry));
	
	sizeOfEntry			=	sizeof(PASEntry);
	if(dataEntry.entryLength < 0)
	{
		err	= dataEntry.entryLength;
		goto error;
	}
	
	err = FSWrite(outRefNum, &sizeOfEntry, &dataEntry);
	if (err != noErr) 	goto error;
	
	
	
	/* Write Out Misc Entry */
	miscEntry.entryID		=	ePas_Misc;
	miscEntry.entryLength	=	sizeof(PASMiscInfo);
	miscEntry.entryOffset	=	sizeof(PASHeader) + (3 * sizeof(PASEntry)) + dataEntry.entryLength;

	sizeOfEntry			=	sizeof(PASEntry);
	err = FSWrite(outRefNum, &sizeOfEntry, &miscEntry);
	if (err != noErr) 	goto error;
	
	
	/* Write Out Resource Entry */
	resourceEntry.entryID		=	ePas_Resource;
	resourceEntry.entryLength	=	-1;
	resourceEntry.entryOffset	=	sizeof(PASHeader) + (3 * sizeof(PASEntry)) + dataEntry.entryLength + miscEntry.entryLength;


	sizeOfEntry			=	sizeof(PASEntry);
	err = FSWrite(outRefNum, &sizeOfEntry, &resourceEntry);
	if (err != noErr) 	goto error;
	
	err =  PAS_encodeData(inSpec, outRefNum);	
	if (err != noErr) 	goto error;
	
	
	err =  PAS_encodeMisc(inSpec, outRefNum);	
	if (err != noErr) 	goto error;
	
	err =  PAS_encodeResource(inSpec, outRefNum);	
	
	if (err == kResFileNotOpened)
	{
		// there was no resource fork
		err = noErr;
	}
	else if (err != noErr)
	{
		goto error;
	}
		
	FSClose(outRefNum);
	
	return noErr;
	
	
	
error:
		
		
	if (outRefNum != kResFileNotOpened)
	{
		FSClose(outRefNum);
	}
		
	FSpDelete( outSpec ) ;
	 
	return err;

}



OSErr PAS_DecodeFile(FSSpec *inSpec, FSSpec *outSpec)
{
	OSErr		err;
	short		inRefNum;
	
	PASHeader	header;
	
	PASEntry 	dataEntry, miscEntry, resourceEntry;
	long		sizeOfEntry;

	if (inSpec == NULL || outSpec == NULL)
		return paramErr;
		
		
	FSpDelete( outSpec ) ;
	
	err = FSpCreate( outSpec, kCreator, kType ,smSystemScript );
	
	if (err != noErr) 	return err;
	
	
	
	err = FSpOpenDF(inSpec, fsRdPerm, &inRefNum);
	
	if (err != noErr)  goto error;

	
	/* Read Header */
		
	err	= PAS_decodeHeader(inRefNum, &header);
	if (err != noErr)  goto error;
	
	if(	header.magicNum != PAS_MAGIC_NUM ||
		header.versionNum != PAS_VERSION)
	{
		err = -1;
		goto error;
	}
	
	
	
	/* Read Data Entry */
	
	
	err = SetFPos(inRefNum, fsFromStart, sizeof(PASHeader));
	if (err != noErr) 	goto error;
	
	sizeOfEntry			=	sizeof(PASEntry);

	err = FSRead(inRefNum, &sizeOfEntry, &dataEntry);
	if (err != noErr) 	goto error;
	
	
	
	
	/* Read Misc Entry */
	
	
	err = SetFPos(inRefNum, fsFromStart, (sizeof(PASHeader) + sizeof(PASEntry)));
	if (err != noErr) 	goto error;
	
	sizeOfEntry			=	sizeof(PASEntry);

	err = FSRead(inRefNum, &sizeOfEntry, &miscEntry);
	if (err != noErr) 	goto error;
	

	/* Read Resource Entry */
	
	
	err = SetFPos(inRefNum, fsFromStart, (sizeof(PASHeader) + (2 * sizeof(PASEntry)))) ;
	if (err != noErr) 	goto error;
	
	sizeOfEntry			=	sizeof(PASEntry);

	err = FSRead(inRefNum, &sizeOfEntry, &resourceEntry);
	if (err != noErr) 	goto error;





	err =  PAS_decodeData(&dataEntry, outSpec, inRefNum);	
	if (err != noErr) 	goto error;

	err =  PAS_decodeMisc(&miscEntry, outSpec, inRefNum);
	if (err != noErr) 	goto error;

	err =  PAS_decodeResource(&resourceEntry, outSpec, inRefNum);	
	if (err == kResFileNotOpened)
	{
		// there was no resource fork
		err = noErr;
	}
	else if (err != noErr)
	{
		goto error;
	}

	
	FSClose(inRefNum);
	
	return noErr;
	
	
	
error:

	if (inRefNum != kResFileNotOpened)
	{
		FSClose(inRefNum);
	}
		
	FSpDelete( outSpec ) ;
	
	return err;
	
}




#pragma mark -


OSErr PAS_encodeResource(FSSpec *inFile, short outRefNum)
{
	OSErr 			err;	
	short			inRefNum;
	PASResFork		resInfo;
	SInt32			currentWrite;
	
	ResType 		*resTypes;
	long			typeCount;
	
	short			*ids;
	long			idCount;
	
	short			oldResFile;
	
	oldResFile=CurResFile();
	inRefNum = FSpOpenResFile(inFile, fsRdPerm);
	if (inRefNum < noErr)	return inRefNum;

	UseResFile(inRefNum);
		
	memset(&resInfo, 0, sizeof(PASResFork));   
	
	PAS_sortTypes(inRefNum, &resTypes, &typeCount);

	resInfo.NumberOfTypes	=	typeCount;
	
	currentWrite	= sizeof(PASResFork);
	
	err = FSWrite(outRefNum, &currentWrite, &resInfo);
	if (err != noErr)	return err;
	
	for (typeCount = 0; ((typeCount < resInfo.NumberOfTypes) && (err == noErr)); typeCount++)
	{
		PAS_sortIDs(inRefNum, resTypes[typeCount], &ids, &idCount);
		err = PAS_flattenResource(resTypes[typeCount], ids, idCount, inRefNum, outRefNum);
		DisposePtr((Ptr)ids);
	}
	
	DisposePtr((Ptr)resTypes);
	
	
	UseResFile(oldResFile);		
	CloseResFile(inRefNum);
	
	return err;
}

OSErr PAS_decodeResource(PASEntry *entry, FSSpec *outFile, short inRefNum)
{
	OSErr 			err = noErr;	
	short			outRefNum;
	PASResFork		info;
	SInt32			infoSize;
	short			oldResFile;
	
	PASResource		pasRes;
	SInt32			pasResSize;
	
	long			bufSize;
	Handle			buffer;
	long			counter=0;
	
	infoSize	=	sizeof(PASResFork);
	
	err = SetFPos(inRefNum, fsFromStart, (*entry).entryOffset );
	if (err != noErr)	return err;

	err	= FSRead( inRefNum, &infoSize, &info);
	if (err != noErr)	return err;

	if(infoSize != sizeof(PASResFork))
	{
		err = -1;
		goto error;
	}
	
	oldResFile=CurResFile();
	
	outRefNum = FSpOpenResFile(outFile, fsRdWrPerm);
	if (outRefNum < noErr)	return outRefNum;
				
	UseResFile(outRefNum);
	
	
	while (1)
	{
		pasResSize	=	sizeof(PASResource);
		err	= FSRead( inRefNum, &pasResSize, &pasRes);
		
		if (err != noErr)
		{
			if(err == eofErr)
				err = noErr;
				
			break;
		}
		
		bufSize	=	pasRes.length;
		buffer	=	NewHandle(bufSize);
		HLock(buffer);
		
		if(buffer == NULL)
		{
			/*  if we did not get our memory, try updateresfile */
		
			HUnlock(buffer);


			UpdateResFile(outRefNum);
			counter=0;
			
			buffer	=	NewHandle(bufSize);
			HLock(buffer);
			
			if(buffer == NULL)
			{
				err = memFullErr;
				break;
			}
		}
		
		err	= FSRead( inRefNum, &bufSize, &(**buffer));
		if (err != noErr && err != eofErr)	break;
		
		AddResource(buffer, pasRes.attrType, pasRes.attrID, pasRes.attrName);
		WriteResource(buffer);
		
		SetResAttrs(buffer, pasRes.attr);
		ChangedResource(buffer);	
		WriteResource(buffer);
		
		ReleaseResource(buffer);	
		
		if (counter++ > 100)
		{
			UpdateResFile(outRefNum);
			counter=0;
		}
	
	}

error:
		
	UseResFile(oldResFile);	
	CloseResFile(outRefNum);
		
	return err;
}

#pragma mark -

OSErr PAS_encodeMisc(FSSpec *inFile, short outRefNum)
{
	OSErr 		err;	
	short		inRefNum;
	PASMiscInfo	infoBlock;
	FInfo		fInfo;
	SInt32		currentRead;
	
	err = FSpOpenDF(inFile, fsRdPerm, &inRefNum);
	if (err != noErr)	return err;
	
	memset(&infoBlock, 0, sizeof(PASMiscInfo));   
	
	err = FSpGetFInfo(inFile, &fInfo);
	if (err != noErr)	return err;
	
	infoBlock.fileType		=	fInfo.fdType;
	infoBlock.fileCreator	=	fInfo.fdCreator;
	infoBlock.fileFlags		=	fInfo.fdFlags;
	
		
	FSClose(inRefNum);
	
	
	inRefNum = FSpOpenResFile(inFile, fsRdPerm);
	if (inRefNum > noErr)
	{
		infoBlock.fileHasResFork	= 1;
		infoBlock.fileResAttrs 		= GetResFileAttrs(inRefNum);
		FSClose(inRefNum);
	}
	else
	{
		infoBlock.fileHasResFork	= 0;
		infoBlock.fileResAttrs 		= 0;
	}
	currentRead	= sizeof(PASMiscInfo);
	
	err = FSWrite(outRefNum, &currentRead, &infoBlock);
	if (err != noErr)	return err;
		
	CloseResFile(inRefNum);
	
	return noErr;
}


OSErr PAS_decodeMisc(PASEntry *entry, FSSpec *outFile, short inRefNum)
{
	OSErr 		err;	
	short		outRefNum;
	PASMiscInfo	info;
	SInt32		infoSize;
	FInfo		theFInfo;
		
	
	infoSize	=	sizeof(PASMiscInfo);
	
	err = SetFPos(inRefNum, fsFromStart, (*entry).entryOffset );
	if (err != noErr)	return err;

	err	= FSRead( inRefNum, &infoSize, &info);
	if (err != noErr)	return err;

	if(infoSize != sizeof(PASMiscInfo))
	{
		return -1;
	}

	err = FSpOpenDF(outFile, fsRdWrPerm, &outRefNum);
	if (err != noErr)	return err;
	
	memset(&theFInfo, 0, sizeof(FInfo));   
		
	theFInfo.fdType		=	info.fileType;
	theFInfo.fdCreator	=	info.fileCreator;	
	theFInfo.fdFlags	=	info.fileFlags;
	
	err = FSpSetFInfo(outFile, &theFInfo);
	if (err != noErr)	return err;
		
	FSClose(outRefNum);
	
	if (info.fileHasResFork)
	{
		outRefNum = FSpOpenResFile(outFile, fsRdWrPerm);
		if (outRefNum < noErr)
		{
			// maybe it does not have one!
			
			FSpCreateResFile(outFile, info.fileCreator, info.fileType, smSystemScript);
			
			outRefNum = FSpOpenResFile(outFile, fsRdWrPerm);	
			if (outRefNum < noErr) 
			{
				return err;
			}
		}
		
		SetResFileAttrs(outRefNum, info.fileResAttrs);
		
		
		CloseResFile(outRefNum);
	}	
	
	return noErr;
	
	

}

#pragma mark -

OSErr PAS_encodeData(FSSpec *inFile, short outRefNum)
{
	OSErr 		err;	
	short		inRefNum;
	Ptr 		buffer;
	SInt32 		currentRead = 	PAS_BUFFER_SIZE;
		
	buffer	=	NewPtr(currentRead);

	err = FSpOpenDF(inFile, fsRdPerm, &inRefNum);
	if (err != noErr)	return err;
	
	while ( currentRead > 0 )
    {
		err	= FSRead( inRefNum, &currentRead, buffer);
		if (err != noErr && err != eofErr)	return err;
		
		err = FSWrite(outRefNum, &currentRead, buffer);
		if (err != noErr)	return err;
	}
	
	FSClose(inRefNum);
	
	DisposePtr(buffer);
	
	return noErr;
}

OSErr PAS_decodeData(PASEntry *entry, FSSpec *outFile, short inRefNum)
{
	OSErr 		err;	
	short		outRefNum;
	Ptr 		buffer;
	SInt32 		currentWrite = 	PAS_BUFFER_SIZE;
	SInt32		totalSize;
	
	
	buffer = NewPtr(currentWrite);
	
	
	err = FSpOpenDF(outFile, fsRdWrPerm, &outRefNum);
	if (err != noErr)	return err;
	
	
	err = SetFPos(inRefNum, fsFromStart, (*entry).entryOffset );
	if (err != noErr)	return err;
	
	err = SetFPos(outRefNum, fsFromStart, 0 );
	if (err != noErr)	return err;
	
	totalSize = (*entry).entryLength;
	
	while(totalSize > 0)
	{	
		currentWrite = PAS_BUFFER_SIZE;

		if (totalSize < currentWrite)
		{
			currentWrite = totalSize;
		}

		err	= FSRead( inRefNum, &currentWrite, buffer);
		if (err != noErr && err != eofErr)	return err;
	
		err = FSWrite(outRefNum, &currentWrite, buffer);
		if (err != noErr)	return err;
		
		totalSize = totalSize - currentWrite;

	}
	
	FSClose(outRefNum);
	
	DisposePtr(buffer);
	
	return noErr;

}

#pragma mark -

OSErr PAS_encodeHeader(short refnum)
{
	PASHeader 	header;
	long		sizeOfHeader;
	OSErr		err;
	
	
	sizeOfHeader = sizeof(PASHeader);
		
	memset(&header, 0, sizeOfHeader);   
	
	header.magicNum 	= PAS_MAGIC_NUM;
	header.versionNum 	= PAS_VERSION;
	header.numEntries 	= 3;
	
	/* Write Out Header */
	err = FSWrite(refnum, &sizeOfHeader, &header);
	
	return err;

}

OSErr PAS_decodeHeader(short refNum, PASHeader *header)
{
	OSErr 	err;
	long 	sizeOfHeader;
	
	sizeOfHeader = sizeof(PASHeader);
	
	memset(header, 0, sizeOfHeader);   
	
	err = FSRead(refNum, &sizeOfHeader, header);
	
	return err;
}

#pragma mark -


unsigned long PAS_getDataSize(FSSpec *spec)
{
	short		refNum;
	OSErr		err;
	Str255 		temp;	
	CInfoPBRec 	cbrec;
	err = FSpOpenDF(spec, fsRdPerm, &refNum);

	memcpy( temp, spec->name, spec->name[0] + 1);

	cbrec.hFileInfo.ioNamePtr 		= temp;
	cbrec.hFileInfo.ioDirID 		= spec->parID;
	cbrec.hFileInfo.ioVRefNum 		= spec->vRefNum;
	cbrec.hFileInfo.ioFDirIndex     = 0;

	err = PBGetCatInfoSync(&cbrec);
	FSClose(refNum);
	
	if(err != noErr)
	{
		cbrec.hFileInfo.ioFlLgLen = err;
	}

	return (cbrec.hFileInfo.ioFlLgLen);
}

short PAS_getResourceID(Handle resource)
{
	ResType			theType;
	Str255			name;
	short			theID;
	
	memset(&name, 0, sizeof(Str255));  
			
	GetResInfo(resource, &theID, &theType, name);
	
	return theID;
}


#pragma mark -
OSErr PAS_flattenResource(ResType type, short *ids, long count, short source, short dest)
{
	long 			idIndex;
	
	
	Handle			resToCopy;
	long			handleLength;
	
	PASResource		pasResource;
	long			pasResLen;
	
	OSErr			err;
	
	for (idIndex=0; idIndex < count; idIndex++)
	{
		if( (type == 'SIZE') && ( ids[idIndex] == 1 || ids[idIndex] == 0  ) )
		{
			/* 	
				We do not want to encode/flatten SIZE 0 or 1 because this
				is the resource that the user can modify.  Most applications
				will not be affected if we remove these resources
			*/
		}
		else
		{	
			resToCopy=Get1Resource(type,ids[idIndex]);
					
			if(!resToCopy)	
			{
				return resNotFound;
			}	
				
			memset(&pasResource, 0, sizeof(PASResource));	
				
			GetResInfo(	resToCopy, 	
						&pasResource.attrID, 
						&pasResource.attrType, 
						pasResource.attrName);	
			
			pasResource.attr = GetResAttrs(resToCopy);
			
			DetachResource(resToCopy);	
			HLock(resToCopy);
			
			pasResource.length 	= GetHandleSize(resToCopy);
			handleLength 		= pasResource.length;
			
			pasResLen			=	sizeof(PASResource);
			
			err = FSWrite(dest, &pasResLen, &pasResource);
			
			if(err != noErr) 
			{
				return err;
			}
			
			err = FSWrite(dest, &handleLength, &(**resToCopy));

			if(err != noErr) 
			{
				return err;
			}
			
			HUnlock(resToCopy);
			DisposeHandle(resToCopy);		
		}
	}
	
	
	return noErr;
}


#pragma mark -

void PAS_sortTypes(short sourceRefNum, ResType **resTypePtr, long *count)
{
	short				oldRef;
	short				typeIndex;
	short				numberOfTypes;
		
	*count	=	-1;
	
	oldRef = CurResFile();
		
	UseResFile(sourceRefNum);

	numberOfTypes = Count1Types();
	
	*resTypePtr	=	(ResType*) NewPtrClear( numberOfTypes * sizeof(OSType) );
	
	for (typeIndex=1; typeIndex <= numberOfTypes; typeIndex++)
	{
		Get1IndType(&(*resTypePtr)[typeIndex-1], typeIndex);
	}
	
	UseResFile(oldRef);
	
	PAS_bubbleSortResType(*resTypePtr, numberOfTypes);
	
	*count = numberOfTypes;
}	


void PAS_sortIDs(short sourceRefNum, OSType theType, short **IdPtr, long *count)
{
	short				oldRef;
	Handle				theHandle;
	short				resCount;
	short				resIndex;
	
	*count	=	-1;
	
	oldRef = CurResFile();
		
	UseResFile(sourceRefNum);

	resCount = Count1Resources(theType);
	
	*IdPtr	=	(short*) NewPtrClear( resCount * sizeof(short) );
	
	for (resIndex=1; resIndex <= resCount; resIndex++)
	{
		theHandle = Get1IndResource(theType, resIndex);
		
		if(theHandle == NULL) return;
	
		(*IdPtr)[resIndex-1] = PAS_getResourceID(theHandle);
		
		ReleaseResource(theHandle);
	}
	
	UseResFile(oldRef);
	
	PAS_bubbleSortIDS(*IdPtr, resCount);
	
	
	*count = resCount;
}

#pragma mark -

void
PAS_bubbleSortResType(ResType *types, long count)
{
	long 	x, y;
	OSType	temp;
	
	for (x=0; x < count-1; x++)      
	{
    	for (y=0; y < count-x-1; y++)        
		{
        	if (types[y] > types[y+1])        
	        {  
				temp=types[y];  
            	types[y]=types[y+1];  
            	types[y+1]=temp;        
        	} 
		}
	}
}

void
PAS_bubbleSortIDS(short *ids, long count)
{
	long 	x, y;
	short	temp;
	
	for (x=0; x < count-1; x++)      
	{
    	for (y=0; y < count-x-1; y++)        
		{
        	if (ids[y] > ids[y+1])        
	        {  
				temp=ids[y];  
            	ids[y]=ids[y+1];  
            	ids[y+1]=temp;        
        	} 
		}
	}
}
