/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/ 
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
 * for the specific language governing rights and limitations under the 
 * License. 
 * 
 * The Original Code is Mozilla Communicator client code, released March
 * 31, 1998. 
 *
 * The Initial Developer of the Original Code is Netscape Communications
 * Corporation. Portions created by Netscape are Copyright (C) 1999
 * Netscape Communications Corporation. All Rights Reserved.  
 * 
 * Contributors:
 *     Samir Gehani <sgehani@netscape.com>
 */


#ifndef _NS_APPLESINGLEDECODER_H_
	#include "nsAppleSingleDecoder.h"
#endif

#include "MoreFilesExtras.h"
#include "MoreDesktopMgr.h"
#include "IterateDirectory.h"
#ifdef MOZILLA_CLIENT
#include "nsISupportsUtils.h"
#endif

/*----------------------------------------------------------------------*
 *   Constructors/Destructor
 *----------------------------------------------------------------------*/
#ifdef MOZILLA_CLIENT
MOZ_DECL_CTOR_COUNTER(nsAppleSingleDecoder)
#endif

nsAppleSingleDecoder::nsAppleSingleDecoder(FSSpec *inSpec, FSSpec *outSpec)
: mInSpec(NULL), 
  mOutSpec(NULL),
  mInRefNum(0),
  mRenameReqd(false)
{
#ifdef MOZILLA_CLIENT
    MOZ_COUNT_CTOR(nsAppleSingleDecoder);
#endif

    if (inSpec && outSpec)
	{
		/* merely point to FSSpecs, not own 'em */
		mInSpec = inSpec;
		mOutSpec = outSpec;
	}
}

nsAppleSingleDecoder::nsAppleSingleDecoder()
: mInSpec(NULL), 
  mOutSpec(NULL),
  mInRefNum(0),
  mRenameReqd(false)
{
#ifdef MOZILLA_CLIENT
    MOZ_COUNT_CTOR(nsAppleSingleDecoder);
#endif
}

nsAppleSingleDecoder::~nsAppleSingleDecoder()
{
	/* not freeing FSSpecs since we don't own 'em */
#ifdef MOZILLA_CLIENT
    MOZ_COUNT_DTOR(nsAppleSingleDecoder);
#endif
}

#pragma mark -

/*----------------------------------------------------------------------*
 *   Public methods
 *----------------------------------------------------------------------*/
OSErr
nsAppleSingleDecoder::Decode()
{
	OSErr		err = noErr;
 	ASHeader 	header;
 	long		bytesRead = sizeof(header);
 	
 	// param check
 	if (!mInSpec || !mOutSpec)
 		return paramErr;
 		
 	// check for existence	
 	FSSpec tmp;
	err = FSMakeFSSpec(mInSpec->vRefNum, mInSpec->parID, mInSpec->name, &tmp);
	if (err == fnfErr)
		return err;
		
	MAC_ERR_CHECK(FSpOpenDF( mInSpec, fsRdPerm, &mInRefNum ));
	MAC_ERR_CHECK(FSRead( mInRefNum, &bytesRead, &header ));
	
	if ( (bytesRead != sizeof(header)) ||
		 (header.magicNum != 0x00051600) ||
		 (header.versionNum != 0x00020000) ||
		 (header.numEntries == 0) ) // empty file?
		 return -1;
	
	// create the outSpec which we'll rename correctly later
	err = FSMakeFSSpec( mInSpec->vRefNum, mInSpec->parID, "\pdecode", mOutSpec );
	if (err!=noErr && err!=fnfErr)
		return err;
	MAC_ERR_CHECK(FSMakeUnique( mOutSpec ));
	MAC_ERR_CHECK(FSpCreate( mOutSpec, 'MOZZ', '????', 0 ));
	
	
	/* Loop through the entries, processing each.
	** Set the time/date stamps last, because otherwise they'll 
	** be destroyed	when we write.
	*/	
	{
		Boolean hasDateEntry = false;
		ASEntry dateEntry;
		long offset;
		ASEntry entry;
		
		for ( int i=0; i < header.numEntries; i++ )
		{	
			offset = sizeof( ASHeader ) + sizeof( ASEntry ) * i;
			MAC_ERR_CHECK(SetFPos( mInRefNum, fsFromStart, offset ));
			
			bytesRead = sizeof(entry);
			MAC_ERR_CHECK(FSRead( mInRefNum, &bytesRead, &entry ));
			if (bytesRead != sizeof(entry))
				return -1;
				
 			if ( entry.entryID == AS_FILEDATES )
 			{
 				hasDateEntry = true;
 				dateEntry = entry;
 			}
 			else
	 			MAC_ERR_CHECK(ProcessASEntry( entry ));
		}
		if ( hasDateEntry )
			MAC_ERR_CHECK(ProcessASEntry( dateEntry ));
	}
	
	// close the inSpec
	FSClose( mInRefNum );
	
	// rename if need be
	if (mRenameReqd)
	{
		FSSpec old;		// delete old version of target file
		
		FSMakeFSSpec(mInSpec->vRefNum, mInSpec->parID, mInSpec->name, &old);
		MAC_ERR_CHECK(FSpDelete(&old));
		MAC_ERR_CHECK(FSpRename(mOutSpec, mInSpec->name));
		
		// reflect change in outSpec
		nsAppleSingleDecoder::PLstrncpy( mOutSpec->name, mInSpec->name, mInSpec->name[0] );
		mOutSpec->name[0] = mInSpec->name[0];
		mRenameReqd = false; // XXX redundant reinit?
	}
	
	if (mType == 'APPL')
	{
        // need to register in desktop database
        DTSetAPPL( NULL, 
                   mOutSpec->vRefNum,
                   mCreator,
                   mOutSpec->parID,
                   mOutSpec->name );
	}
	
 	return err;
}

OSErr
nsAppleSingleDecoder::Decode(FSSpec *inSpec, FSSpec *outSpec)
{
	OSErr 	err = noErr;
	
	// param check
	if (inSpec && outSpec)
	{
		mInSpec = inSpec;		// reinit
		mOutSpec = outSpec;
		mRenameReqd = false; 
	}
	else
		return paramErr;
		
	err = Decode();
	
	return err;
}

pascal void
DecodeDirIterateFilter(const CInfoPBRec * const cpbPtr, Boolean *quitFlag, void *yourDataPtr)
{	
	OSErr					err = noErr;
	FSSpec 					currFSp, outFSp;
	nsAppleSingleDecoder* 	thisObj = NULL;
	Boolean					isDir = false;
	long					dummy;
	
	// param check
	if (!yourDataPtr || !cpbPtr || !quitFlag)
		return;
		
	*quitFlag = false;
	
	// extract 'this' -- an nsAppleSingleDecoder instance
	thisObj = (nsAppleSingleDecoder*) yourDataPtr;
	
	// make an FSSpec from the CInfoPBRec*
	err = FSMakeFSSpec(cpbPtr->hFileInfo.ioVRefNum, cpbPtr->hFileInfo.ioFlParID, 
						cpbPtr->hFileInfo.ioNamePtr, &currFSp);
	if (err == noErr)
	{
		FSpGetDirectoryID(&currFSp, &dummy, &isDir);
		
		// if current FSSpec is file
		if (!isDir)
		{
			// if file is in AppleSingle format
			if (nsAppleSingleDecoder::IsAppleSingleFile(&currFSp))
			{
				// decode file
				thisObj->Decode(&currFSp, &outFSp);
			}
		}
		else
		{
			// else if current FSSpec is folder ignore
			// XXX never reached?
			return;
		}
	}
}

OSErr 
nsAppleSingleDecoder::DecodeFolder(FSSpec *aFolder)
{
	OSErr	err = noErr;
	long	dummy;
	Boolean	isDir = false;
	
	// check that FSSpec is folder
	if (aFolder)
	{
		FSpGetDirectoryID(aFolder, &dummy, &isDir);
		if (!isDir)
			return dirNFErr;
	}
	
	// recursively enumerate contents of folder (maxLevels=0 means recurse all)
	FSpIterateDirectory(aFolder, 0, DecodeDirIterateFilter, (void*)this);
			
	return err;
}

Boolean
nsAppleSingleDecoder::IsAppleSingleFile(FSSpec *inSpec)
{
	OSErr	err;
	Boolean bAppleSingle = false;
	short	inRefNum;
	UInt32	magic;
	long 	bytesRead = sizeof(magic);
	
	// param checks
	if (!inSpec)
		return false;
		
	// check for existence
	FSSpec tmp;
	err = FSMakeFSSpec(inSpec->vRefNum, inSpec->parID, inSpec->name, &tmp);
	if (err!=noErr)
		return false;
	
	// open and read the magic number len bytes	
	err = FSpOpenDF( inSpec, fsRdPerm, &inRefNum );
	if (err!=noErr)
		return false;
		
	err = FSRead( inRefNum, &bytesRead, &magic );
	if (err!=noErr)
		return false;
		
	FSClose(inRefNum);
	if (bytesRead != sizeof(magic))
		return false;

	// check if bytes read match magic number	
	bAppleSingle = (magic == 0x00051600);
	return bAppleSingle;
}

#pragma mark -

/*----------------------------------------------------------------------*
 *   Private methods
 *----------------------------------------------------------------------*/
OSErr
nsAppleSingleDecoder::ProcessASEntry(ASEntry inEntry)
{
	switch (inEntry.entryID)
	{
		case AS_DATA:
			return ProcessDataFork( inEntry );
			break;
		case AS_RESOURCE:
			return ProcessResourceFork( inEntry );
			break;
		case AS_REALNAME:
			ProcessRealName( inEntry );
			break;
			// return 0;	// Ignore these errors in ASD   <--- XXX remove
		case AS_COMMENT:
//			return ProcessComment( inEntry );
			break;
		case AS_ICONBW:
//			return ProcessIconBW( inEntry );
			break;
		case AS_ICONCOLOR:
//			return ProcessIconColor( inEntry );
			break;
		case AS_FILEDATES:
			return ProcessFileDates( inEntry );
			break;
		case AS_FINDERINFO:
			return ProcessFinderInfo( inEntry );
			break;
		case AS_MACINFO:
			return ProcessMacInfo( inEntry );
			break;
		case AS_PRODOSINFO:
		case AS_MSDOSINFO:
		case AS_AFPNAME:
		case AS_AFPINFO:
		case AS_AFPDIRID:
		default:
			return 0;
	}
	return 0;
}

OSErr
nsAppleSingleDecoder::ProcessDataFork(ASEntry inEntry)
{
	OSErr	err = noErr;
	SInt16	refNum;
	
	/* Setup the files */
	err = FSpOpenDF (mOutSpec, fsWrPerm,  &refNum);

	if ( err == noErr )
		err = EntryToMacFile( inEntry, refNum );
	
	FSClose( refNum );
	return err;
}

OSErr
nsAppleSingleDecoder::ProcessResourceFork(ASEntry inEntry)
{
	OSErr	err = noErr;
	SInt16	refNum;
		
	err = FSpOpenRF(mOutSpec, fsWrPerm,  &refNum);

	if ( err == noErr )
		err = EntryToMacFile( inEntry, refNum );
	
	FSClose( refNum );
	return err;
}

OSErr
nsAppleSingleDecoder::ProcessRealName(ASEntry inEntry)
{
	OSErr	err = noErr;
	Str255	newName;
	long	bytesRead;
	
	if ( inEntry.entryLength > 32 )	/* Max file name length for the Mac */
		return -1;

	MAC_ERR_CHECK(SetFPos(mInRefNum, fsFromStart, inEntry.entryOffset));
	
	bytesRead = inEntry.entryLength;
	MAC_ERR_CHECK(FSRead(mInRefNum, &bytesRead, &newName[1]));
	if (bytesRead != inEntry.entryLength)
		return -1;
		
	newName[0] = inEntry.entryLength;
	err = FSpRename(mOutSpec, newName);
	if (err == dupFNErr)
	{
		// if we are trying to rename temp decode file to src name, rename later
		if (nsAppleSingleDecoder::PLstrcmp(newName, mInSpec->name))
		{
			mRenameReqd = true;
			return noErr;
		}
		
		FSSpec old;		// delete old version of target file
		
		FSMakeFSSpec(mOutSpec->vRefNum, mOutSpec->parID, newName, &old);
		MAC_ERR_CHECK(FSpDelete(&old));
		MAC_ERR_CHECK(FSpRename(mOutSpec, newName));
	}
	nsAppleSingleDecoder::PLstrncpy( mOutSpec->name, newName, inEntry.entryLength );
	mOutSpec->name[0] = inEntry.entryLength;

	return err;
}

OSErr
nsAppleSingleDecoder::ProcessFileDates(ASEntry inEntry)
{
	OSErr		err = noErr;
	ASFileDates dates;
	long		bytesRead;
	
	if ( inEntry.entryLength != sizeof(dates) )	/* Max file name length for the Mac */
		return -1;
	
	MAC_ERR_CHECK(SetFPos(mInRefNum, fsFromStart, inEntry.entryOffset));
	
	bytesRead = inEntry.entryLength;
	MAC_ERR_CHECK(FSRead(mInRefNum, &bytesRead, &dates));
	if (bytesRead != inEntry.entryLength)
		return -1;
		
	Str31 name;
	nsAppleSingleDecoder::PLstrncpy(name, mOutSpec->name, mOutSpec->name[0]);
	name[0] = mOutSpec->name[0];
	CInfoPBRec pb;
	pb.hFileInfo.ioNamePtr = &name[0];
	pb.hFileInfo.ioVRefNum = mOutSpec->vRefNum;
	pb.hFileInfo.ioDirID = mOutSpec->parID;
	pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	err = PBGetCatInfoSync(&pb);
	if ( err != noErr )
		return -1;
#define YR_2000_SECONDS 3029529600
	pb.hFileInfo.ioFlCrDat = dates.create + YR_2000_SECONDS;
	pb.hFileInfo.ioFlMdDat = dates.modify + YR_2000_SECONDS;
	pb.hFileInfo.ioFlBkDat = dates.backup + YR_2000_SECONDS;
	/* Not sure if mac has the last access time */
	
	nsAppleSingleDecoder::PLstrncpy(name, mOutSpec->name, mOutSpec->name[0]);
	name[0] = mOutSpec->name[0];
	pb.hFileInfo.ioNamePtr = name;
	pb.hFileInfo.ioVRefNum = mOutSpec->vRefNum;
	pb.hFileInfo.ioDirID = mOutSpec->parID;
	pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	err = PBSetCatInfo(&pb, false);
	
	return err;
}

OSErr
nsAppleSingleDecoder::ProcessFinderInfo(ASEntry inEntry)
{
	OSErr			err = noErr;
	ASFinderInfo 	info;
	long			bytesRead;
	
	if (inEntry.entryLength != sizeof( ASFinderInfo ))
		return -1;

	MAC_ERR_CHECK(SetFPos(mInRefNum, fsFromStart, inEntry.entryOffset));

	bytesRead = sizeof(info);
	MAC_ERR_CHECK(FSRead(mInRefNum, &bytesRead, &info));
	if (bytesRead != inEntry.entryLength)
		return -1;
		
	err = FSpSetFInfo(mOutSpec, &info.ioFlFndrInfo);
	if (err!=noErr && err!=fnfErr)
		return err;

	Str31 name;
	nsAppleSingleDecoder::PLstrncpy(name, mOutSpec->name, mOutSpec->name[0]);
	name[0] = mOutSpec->name[0];
	CInfoPBRec pb;
	pb.hFileInfo.ioNamePtr = name;
	pb.hFileInfo.ioVRefNum = mOutSpec->vRefNum;
	pb.hFileInfo.ioDirID = mOutSpec->parID;
	pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	MAC_ERR_CHECK(PBGetCatInfoSync(&pb));
	
	pb.hFileInfo.ioNamePtr = name;
	pb.hFileInfo.ioVRefNum = mOutSpec->vRefNum;
	pb.hFileInfo.ioDirID = mOutSpec->parID;
	pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	pb.hFileInfo.ioFlXFndrInfo = info.ioFlXFndrInfo;
	err = PBSetCatInfo(&pb, false);
    
    // save the creator and file type so we can set it on the renamed spec
    mCreator = info.ioFlFndrInfo.fdCreator;
    mType = info.ioFlFndrInfo.fdType;

	return err;
}

OSErr
nsAppleSingleDecoder::ProcessMacInfo(ASEntry inEntry)
{
	OSErr		err = noErr;
	ASMacInfo 	info;
	long 		bytesRead;
	
	if (inEntry.entryLength != sizeof( ASMacInfo ))
		return -1;

	MAC_ERR_CHECK(SetFPos(mInRefNum, fsFromStart, inEntry.entryOffset));

	bytesRead = sizeof(info);
	MAC_ERR_CHECK(FSRead(mInRefNum, &bytesRead, &info));
	if (bytesRead != inEntry.entryLength)
		return -1;
	
	Str31 name;
	nsAppleSingleDecoder::PLstrncpy(name, mOutSpec->name, mOutSpec->name[0]);
	name[0] = mOutSpec->name[0];
	CInfoPBRec pb;
	pb.hFileInfo.ioNamePtr = name;
	pb.hFileInfo.ioVRefNum = mOutSpec->vRefNum;
	pb.hFileInfo.ioDirID = mOutSpec->parID;
	pb.hFileInfo.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
	MAC_ERR_CHECK(PBGetCatInfoSync(&pb));
	
	pb.hFileInfo.ioNamePtr = name;
	pb.hFileInfo.ioVRefNum = mOutSpec->vRefNum;
	pb.hFileInfo.ioDirID = mOutSpec->parID;
	pb.hFileInfo.ioFlAttrib = info.ioFlAttrib;
	err = PBSetCatInfo(&pb, false);
	
	return err;
}

OSErr
nsAppleSingleDecoder::EntryToMacFile(ASEntry inEntry, UInt16 inTargetSpecRefNum)
{
#define BUFFER_SIZE 8192

	OSErr	err = noErr;
	char 	buffer[BUFFER_SIZE];
	long 	totalRead = 0, bytesRead, bytesToWrite;

	MAC_ERR_CHECK(SetFPos( mInRefNum, fsFromStart, inEntry.entryOffset ));

	while ( totalRead < inEntry.entryLength )
	{
// Should we yield in here?
		bytesRead = BUFFER_SIZE;
		err = FSRead( mInRefNum, &bytesRead, buffer );
		if (err!=noErr && err!=eofErr)
			return err;
			
		if ( bytesRead <= 0 )
			return -1;
		bytesToWrite = totalRead + bytesRead > inEntry.entryLength ? 
									inEntry.entryLength - totalRead :
									bytesRead;

		totalRead += bytesRead;
		MAC_ERR_CHECK(FSWrite(inTargetSpecRefNum, &bytesToWrite, buffer));
	}	
	
	return err;
}

#pragma mark -

OSErr 
nsAppleSingleDecoder::DTSetAPPL(Str255 volName, short vRefNum, OSType creator, 
                                long applParID, Str255 applName)
{
    OSErr err;
    DTPBRec *pb = NULL;
    short dtRefNum;
    short realVRefNum;
    Boolean newDTDatabase;
    
    /* get the real vRefnum */
    err = DetermineVRefNum(volName, vRefNum, &realVRefNum);
    if (err == noErr)
    {
        err = DTOpen(volName, vRefNum, &dtRefNum, &newDTDatabase);
        if (err == noErr && !newDTDatabase)
        {
            pb = (DTPBRec*) NewPtrClear( sizeof(DTPBRec) );
            
            if (pb==NULL) return -1;

            pb->ioNamePtr      =   applName;
            pb->ioDTRefNum     =   dtRefNum;
            pb->ioDirID        =   applParID;
            pb->ioFileCreator  =   creator;

            err = PBDTAddAPPLSync(pb);
            
            if (pb) DisposePtr((Ptr)pb);
        }        
    }
    
    return err;
}

OSErr
nsAppleSingleDecoder::FSMakeUnique(FSSpec *ioSpec)
{
	OSErr			err = noErr;
	Boolean 		bUnique = false;
	FSSpec			tmp;
	long				uniqueID = 0;
	Str255			name;
	short			i, j;
	unsigned char	puniqueID[16];
	char			*cuniqueIDPtr;

	// grab suggested name from in-out FSSpec
	nsAppleSingleDecoder::PLstrncpy(name, ioSpec->name, ioSpec->name[0]);
	name[0] = ioSpec->name[0];
	
	for (i=0; i<65536; i++)		// prevent infinite loop
	{	
		if (!bUnique)
		{
			err = FSMakeFSSpec( ioSpec->vRefNum, ioSpec->parID, name, &tmp );
			if (err == fnfErr)
			{
				bUnique = true;
				break;
			}
			else if (err == noErr)	// file already exists
			{
				// grab suggested name from in-out FSSpec
				nsAppleSingleDecoder::PLstrncpy(name, ioSpec->name, ioSpec->name[0]);
				name[0] = ioSpec->name[0];
				
				// attempt to create a new unique file name
				nsAppleSingleDecoder::PLstrncat( name, "\p-", 1 );
				
				// tack on digit(s)
				cuniqueIDPtr = nsAppleSingleDecoder::ltoa(uniqueID++);
				puniqueID[0] = strlen(cuniqueIDPtr);
				for (j=0; j<strlen(cuniqueIDPtr); j++)
				{
					puniqueID[j+1] = cuniqueIDPtr[j];
				}
				
				nsAppleSingleDecoder::PLstrncat( name, puniqueID, puniqueID[0] );
				DisposePtr((Ptr)cuniqueIDPtr);
			}
			else
				return err;
		}
	}
	
	// put back unique name into in-out FSSpec
	nsAppleSingleDecoder::PLstrncpy(ioSpec->name, name, name[0]);
	ioSpec->name[0] = name[0];
	
	return noErr;
}		

/*----------------------------------------------------------------------*
 *   Utilities
 *----------------------------------------------------------------------*/
char *
nsAppleSingleDecoder::ltoa(long n)
{
	char *s;
	int i, j, sign, tmp;
	
	/* check sign and convert to positive to stringify numbers */
	if ( (sign = n) < 0)
		n = -n;
	i = 0;
	s = (char*) malloc(sizeof(char));
	
	/* grow string as needed to add numbers from powers of 10 down till none left */
	do
	{
		s = (char*) realloc(s, (i+1)*sizeof(char));
		s[i++] = n % 10 + '0';  /* '0' or 30 is where ASCII numbers start from */
		s[i] = '\0';
	}
	while( (n /= 10) > 0);	
	
	/* tack on minus sign if we found earlier that this was negative */
	if (sign < 0)
	{
		s = (char*) realloc(s, (i+1)*sizeof(char));
		s[i++] = '-';
	}
	s[i] = '\0';
	
	/* pop numbers (and sign) off of string to push back into right direction */
	for (i = 0, j = strlen(s) - 1; i < j; i++, j--)
	{
		tmp = s[i];
		s[i] = s[j];
		s[j] = tmp;
	}
	
	return s;
}

StringPtr 	
nsAppleSingleDecoder::PLstrncpy(StringPtr dst, ConstStr255Param src, short max)
{
	int srcLen = src[0];
	if (srcLen > max)
		srcLen = max;
	dst[0] = srcLen;	
	memcpy(&dst[1], &src[1], srcLen);
	return dst;	
}

StringPtr 	
nsAppleSingleDecoder::PLstrncat(StringPtr dst, ConstStr255Param src, short max)
{
	int srcLen = src[0], dstLen = dst[0];
	if (srcLen > max)
		srcLen = max;
	dst[0] += srcLen;
	memcpy(&dst[dstLen+1], &src[1], srcLen);
	return dst;
}

Boolean
nsAppleSingleDecoder::PLstrcmp(StringPtr str1, StringPtr str2)
{
	Boolean	bEqual = true;
	
	// check for same length
	if (str1[0] == str2[0])
	{
		// verify mem blocks match
		if (0 != memcmp(&str1[1], &str2[1], str1[0]))
			bEqual = false;
	}
	else
		bEqual = false;
		
	return bEqual;
}
