/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  Please
 * see the License for the specific language governing rights and
 * limitations under the License.
 *
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */
 
 
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <unistd.h>
 
 #include "RTPFileDefs.h"
 #include "QTRTPFile.h"
 #include "QTHintTrack.h"
 #include "OSHeaders.h"


 class QTRTPGenFile : public QTRTPFile
 {
 	public:
 	
	 	QTRTPGenFile() : QTRTPFile() {}
	 	virtual ~QTRTPGenFile() {}
	 	
	 	// Accessors
 		UInt32		GetNumHintTracks() { return fNumHintTracks; }
		RTPTrackListEntry* GetFirstTrack() { return fFirstTrack; }
 };
 
 
UInt8* WriteTempFile(QTRTPFile* inQTRTPFile, int inTempFile, UInt32* outNumBlocks);



int main(int argc, char *argv[])
{

	if (argc <= 1)
	{
		printf("Usage: rtpfilegen *hintedqtfilename*\n");
		exit(0);
	}
	
	char* theFileName = argv[1];
	
	QTRTPFile::Initialize();
	
	// Init our qt file
	QTRTPGenFile theFile;
	QTRTPFile::ErrorCode initErr = theFile.Initialize(theFileName);
	switch (initErr)
	{
		case QTRTPFile::errFileNotFound:
			printf("Movie file not found\n"); exit(0);
		case QTRTPFile::errInvalidQuickTimeFile:
			printf("File is not a hinted quicktime file\n"); exit(0);
		case QTRTPFile::errNoHintTracks:
			printf("File has no hint tracks\n"); exit(0);
		case QTRTPFile::errInternalError:
			printf("Internal error\n"); exit(0);
        
        case QTRTPFile::errNoError:
        case QTRTPFile::errTrackIDNotFound:
        case QTRTPFile::errCallAgain:
            //noops
            break;
	}

	// Open our output file
	char* outputFileName = new char[::strlen(theFileName) + 5];
	::strcpy(outputFileName, theFileName);
	::strcat(outputFileName, ".rtp");
	int theOutFile = open(outputFileName, O_WRONLY | O_CREAT | O_TRUNC);
	if (theOutFile == -1)
	{	printf("Failed to open output file at %s.\n", outputFileName); exit(0);	}
		
	// Open a temp file
	char* tempFileName = new char[::strlen(theFileName) + 6];
	::strcpy(tempFileName, theFileName);
	::strcat(tempFileName, ".temp");
	int theTempFile = open(tempFileName, O_WRONLY | O_CREAT | O_TRUNC);
	if (theTempFile == -1)
	{	printf("Failed to open temp file at %s.\n", tempFileName); exit(0); }
	
	// Setup all our tracks
	UInt32 theMaxTrackID = 0; // These variables are needed to write the file header
	UInt32 theNumTracks = 0;
	QTRTPFile::RTPTrackListEntry* curTrack = theFile.GetFirstTrack();
	while (curTrack != NULL)
	{
		if (theMaxTrackID < curTrack->TrackID)
			theMaxTrackID = curTrack->TrackID;
		
		theNumTracks++;
		(void)theFile.AddTrack(curTrack->TrackID);
		// Make the cookie be the track ID, so we know what the track ID is when 
		// we are retreiving packets
		theFile.SetTrackCookies(curTrack->TrackID, (void*)curTrack->TrackID, 0);
		
		curTrack = curTrack->NextTrack;
	}
	
	// Seek to time 0
	(void)theFile.Seek(0);
	
	// Write status
	printf("Generating RTP packets to temp file at %s...\n", tempFileName);
	
	// Write out all the packets to the temp file
	UInt32 theNumBlocks = 0;
	UInt8* theBlockArray = WriteTempFile(&theFile, theTempFile, &theNumBlocks);
	::close(theTempFile);
	
	// Write status
	printf("Generating RTP file at %s. Copying packets...\n", outputFileName);

	// Get SDP
	int theSDPLen = 0;
	char* theSDPFile = theFile.GetSDPFile(&theSDPLen);
	
	// Write file header
	RTPFileHeader theHeader;
	theHeader.fSDPLen = theSDPLen;
	theHeader.fNumTracks = theFile.GetNumHintTracks();
	theHeader.fMaxTrackID = theMaxTrackID;
	theHeader.fBlockMapSize = theNumBlocks;
	theHeader.fDataStartPos = theNumBlocks + theSDPLen + sizeof(RTPFileHeader) +
										(sizeof(RTPFileTrackInfo) * theNumTracks);
	theHeader.fMovieDuration = theFile.GetMovieDuration();
	
	int theErr = ::write(theOutFile, &theHeader, sizeof(theHeader));
	if (theErr != sizeof(theHeader))
	{	printf("Write operation failed on output file\n"); exit(0); }
	
	// Write the SDP data
	theErr = ::write(theOutFile, theSDPFile, theSDPLen);
	if (theErr != theSDPLen)
	{	printf("Write operation failed on output file\n"); exit(0); }
		
	// Write track info
	curTrack = theFile.GetFirstTrack();
	while (curTrack != NULL)
	{
		RTPFileTrackInfo theTrackInfo;
		theTrackInfo.fID = curTrack->TrackID;
		theTrackInfo.fTimescale = curTrack->HintTrack->GetRTPTimescale();
		theTrackInfo.fBytesInTrack = curTrack->HintTrack->GetTotalRTPBytes();
		theTrackInfo.fDuration = curTrack->HintTrack->GetDurationInSeconds();

		theErr = ::write(theOutFile, &theTrackInfo, sizeof(theTrackInfo));
		if (theErr != sizeof(theTrackInfo))
		{	printf("Write operation failed on output file\n"); exit(0); }

		curTrack = curTrack->NextTrack;
	}
	
	// Write the block map
	theErr = ::write(theOutFile, theBlockArray, theNumBlocks);
	if (theErr != theNumBlocks)
	{	printf("Write operation failed on output file\n"); exit(0); }
	
	//Copy the temp file into the out file
	theTempFile = open(tempFileName, O_RDONLY);
	if (theTempFile == -1)
	{	printf("Failed to open temp file for reading at %s.\n", tempFileName); exit(0); }
	
	while (true)
	{
		char copyBuffer[kBlockSize];
	
		// Read a block out of the temp file
		theErr = ::read(theTempFile, &copyBuffer[0], kBlockSize);
		if (theErr <= 0)
			break;
			
		int lenWritten = ::write(theOutFile, &copyBuffer[0], theErr);
		if (theErr != lenWritten)
		{	printf("Write operation failed on output file\n"); exit(0); }
	}
	
	printf("RTP file generation complete\n");
	
	// Delete the temp file
	(void)::unlink(tempFileName);
	
	// close the output file
	::close(theOutFile);
	
	// We're done!
}


// Returns the complete block map
UInt8* WriteTempFile(QTRTPFile* inQTRTPFile, int inTempFile, UInt32* outNumBlocks)
{
	int theErr = -1;
	UInt32 theNumPackets = 0;
	
	// Write all packets to the temp file. Pad out blocks appropriately
	UInt32 offsetInBlock = 0;
	
	// As we write out packets, build our block map
	UInt8* theBlockMap = new UInt8[1024];
	UInt32 curBlockMapSize = 1024;
	UInt32 curBlockMapIndex = 0;
	Float64 theLastBlockTime = 0;
	
	while (true)
	{
		RTPFilePacket thePacketHeader;
		char* thePacketData = NULL;
		int thePacketLength = 0;
		
		thePacketHeader.fTransmitTime = inQTRTPFile->GetNextPacket(&thePacketData, &thePacketLength);
		if (thePacketData == NULL)
			// We're done with all tracks
			break;
					
		thePacketHeader.fPacketLength = thePacketLength;
		thePacketHeader.fTrackID = (UInt32)inQTRTPFile->GetLastPacketTrack()->Cookie1;

		// Make sure there is enough room in the current block for this
		// packet plus a header for the next packet. If not, move onto the next block
		if ((offsetInBlock + thePacketHeader.fPacketLength + (2 * sizeof(RTPFilePacket))) > kBlockSize)
		{
			// Write out a pad packet
			RTPFilePacket pad;
			pad.fTrackID |= kPaddingBit;
			
			theErr = ::write(inTempFile, &pad, sizeof(pad));
			if (theErr < sizeof(pad))
			{	printf("Write operation failed on temp file\n"); exit(0); }
				
			offsetInBlock += sizeof(pad);
			SInt32 spaceRemaining = kBlockSize - offsetInBlock;
			Assert(spaceRemaining >= 0);
			
			// Fill out the rest of this block with crap
			char* dumpBuf = new char[spaceRemaining];
			::memset(dumpBuf, 0xFF, spaceRemaining);//Fill with FFs so we can debug easier
			theErr = ::write(inTempFile, dumpBuf, spaceRemaining);
			if (theErr < spaceRemaining)
			{	printf("Write operation failed on temp file\n"); exit(0); }
				
			delete [] dumpBuf;
			
			offsetInBlock = 0;
		}
		
		// If this is the first packet in a block, we should mark the time in the
		// Write the packet header and the packet data
		if (offsetInBlock == 0)
		{
			// What is the offset between the first packet in the last block and this block?
			Float64 theOffset = thePacketHeader.fTransmitTime - theLastBlockTime;
			if (theOffset < 0)
				theOffset = 0; //This may happen with the stupid negative times
			theLastBlockTime = thePacketHeader.fTransmitTime;
			if (theLastBlockTime < 0)
				theLastBlockTime = 0; //This may happen with the stupid negative times
				
			Assert(theOffset < 255);
			theBlockMap[curBlockMapIndex] = (UInt8)theOffset;

			// We may need to reallocate the block array
			curBlockMapIndex++;
			if (curBlockMapIndex == curBlockMapSize)
			{
				UInt8* newBlockArray = new UInt8[curBlockMapSize * 2];
				::memcpy(newBlockArray, theBlockMap, curBlockMapSize);
				curBlockMapSize *= 2;
				
				delete [] theBlockMap;
				theBlockMap = newBlockArray;
			}
		}

		theNumPackets++;
		theErr = ::write(inTempFile, &thePacketHeader, sizeof(thePacketHeader));
		if (theErr < sizeof(thePacketHeader))
		{	printf("Write operation failed on temp file\n"); exit(0); }

		theErr = ::write(inTempFile, thePacketData, thePacketLength);
		if (theErr < thePacketLength)
		{	printf("Write operation failed on temp file\n"); exit(0); }
		
		// update our block offset
		offsetInBlock += thePacketLength + sizeof(thePacketHeader);
	}
	
	printf("Finished writing packets. Wrote %d packets to temp file.\n", theNumPackets);
	*outNumBlocks = curBlockMapIndex-1;
	return theBlockMap;
}