/*
 *
 * @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@
 *
 */
// $Id: QTTrack.cpp,v 1.9 2001/11/20 01:08:47 murata Exp $
//
// QTTrack:
//   The central point of control for a track in a QTFile.


// -------------------------------------
// Includes
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "QTFile.h"

#include "QTAtom.h"
#include "QTAtom_dref.h"
#include "QTAtom_elst.h"
#include "QTAtom_mdhd.h"
#include "QTAtom_tkhd.h"
#include "QTAtom_stco.h"
#include "QTAtom_stsc.h"
#include "QTAtom_stsd.h"
#include "QTAtom_stss.h"
#include "QTAtom_stsz.h"
#include "QTAtom_stts.h"

#include "QTTrack.h"

#include "OSMemory.h"

// -------------------------------------
// Macros
//
#define DEBUG_PRINT(s) if(fDebug) printf s
#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) printf s

// -------------------------------------
// Constructors and destructors
//
QTTrack::QTTrack(QTFile * File, QTFile::AtomTOCEntry * Atom, Bool16 Debug, Bool16 DeepDebug)
	: fDebug(Debug), fDeepDebug(DeepDebug),
	  fFile(File),
	  fIsInitialized(false),
	  fTrackHeaderAtom(NULL),
	  fTrackName(NULL),
	  fMediaHeaderAtom(NULL),
	  fEditListAtom(NULL), fDataReferenceAtom(NULL),
	  fTimeToSampleAtom(NULL), fSampleToChunkAtom(NULL), fSampleDescriptionAtom(NULL),
	  fChunkOffsetAtom(NULL), fSampleSizeAtom(NULL), fSyncSampleAtom(NULL),
	  fFirstEditMediaTime(0)
{
	// Temporary vars
	QTFile::AtomTOCEntry	*tempTOCEntry;
	
	//
	// Make a copy of the TOC entry.
	memcpy(&fTOCEntry, Atom, sizeof(QTFile::AtomTOCEntry));

	//
	// Load in the track header atom for this track.
	if( !fFile->FindTOCEntry(":tkhd", &tempTOCEntry, &fTOCEntry) )
		return;
	
	fTrackHeaderAtom = NEW QTAtom_tkhd(fFile, tempTOCEntry, fDebug, fDeepDebug);
	if( fTrackHeaderAtom == NULL )
		return;
	if( !fTrackHeaderAtom->Initialize() ) {
		delete fTrackHeaderAtom;
		fTrackHeaderAtom = NULL;
	}
	
}

QTTrack::~QTTrack(void)
{
	//
	// Free our variables
	if( fTrackHeaderAtom != NULL )
		delete fTrackHeaderAtom;
	if( fTrackName != NULL )
		delete[] fTrackName;

	if( fMediaHeaderAtom != NULL )
		delete fMediaHeaderAtom;

	if( fEditListAtom != NULL )
		delete fEditListAtom;
	if( fDataReferenceAtom != NULL )
		delete fDataReferenceAtom;

	if( fTimeToSampleAtom != NULL )
		delete fTimeToSampleAtom;
	if( fSampleToChunkAtom != NULL )
		delete fSampleToChunkAtom;
	if( fSampleDescriptionAtom != NULL )
		delete fSampleDescriptionAtom;
	if( fChunkOffsetAtom != NULL )
		delete fChunkOffsetAtom;
	if( fSampleSizeAtom != NULL )
		delete fSampleSizeAtom;
	if( fSyncSampleAtom != NULL )
		delete fSyncSampleAtom;
}



// -------------------------------------
// Initialization functions
//
QTTrack::ErrorCode QTTrack::Initialize(void)
{
	// Temporary vars
	QTFile::AtomTOCEntry	*tempTOCEntry;


	//
	// Don't initialize more than once.
	if( IsInitialized() )
		return errNoError;

	//
	// Make sure that we were able to read in our track header atom.
	if( fTrackHeaderAtom == NULL )
		return errInvalidQuickTimeFile;

	//
	// See if this track has a name and load it in.
	if( fFile->FindTOCEntry(":udta:name", &tempTOCEntry, &fTOCEntry) ) {
		fTrackName = NEW char[tempTOCEntry->AtomDataLength + 1];
		if( fTrackName != NULL )
			fFile->Read(tempTOCEntry->AtomDataPos, fTrackName, tempTOCEntry->AtomDataLength);
	}


	//
	// Load in the media header atom for this track.
	if( !fFile->FindTOCEntry(":mdia:mdhd", &tempTOCEntry, &fTOCEntry) )
		return errInvalidQuickTimeFile;
	
	fMediaHeaderAtom = NEW QTAtom_mdhd(fFile, tempTOCEntry, fDebug, fDeepDebug);
	if( fMediaHeaderAtom == NULL )
		return errInternalError;
	if( !fMediaHeaderAtom->Initialize() )
		return errInvalidQuickTimeFile;

	
	//
	// Load in the edit list atom for this track.
	DEEP_DEBUG_PRINT(("Searching track #%lu 'elst' atom.\n", GetTrackID()));
	if( fFile->FindTOCEntry(":edts:elst", &tempTOCEntry, &fTOCEntry) ) {
		fEditListAtom = NEW QTAtom_elst(fFile, tempTOCEntry, fDebug, fDeepDebug);
		if( fEditListAtom == NULL )
			return errInternalError;
		if( !fEditListAtom->Initialize() )
			return errInvalidQuickTimeFile;

		//
		// Compute the first edit's media time.
		fFirstEditMediaTime = (UInt32)((GetFirstEditMovieTime() / fFile->GetTimeScale()) * GetTimeScale());
	} else {
		fEditListAtom = NULL;
	}


	//
	// Load in the data reference atom for this track.
	if( !fFile->FindTOCEntry(":mdia:minf:dinf:dref", &tempTOCEntry, &fTOCEntry) )
		return errInvalidQuickTimeFile;
	
	fDataReferenceAtom = NEW QTAtom_dref(fFile, tempTOCEntry, fDebug, fDeepDebug);
	if( fDataReferenceAtom == NULL )
		return errInternalError;
	if( !fDataReferenceAtom->Initialize() )
		return errInvalidQuickTimeFile;

	
	//
	// Load in the sample table atoms.
	if( !fFile->FindTOCEntry(":mdia:minf:stbl:stts", &tempTOCEntry, &fTOCEntry) )
		return errInvalidQuickTimeFile;

	fTimeToSampleAtom = NEW QTAtom_stts(fFile, tempTOCEntry, fDebug, fDeepDebug);
	if( fTimeToSampleAtom == NULL )
		return errInternalError;
	if( !fTimeToSampleAtom->Initialize() )
		return errInvalidQuickTimeFile;


	if( !fFile->FindTOCEntry(":mdia:minf:stbl:stsc", &tempTOCEntry, &fTOCEntry) )
		return errInvalidQuickTimeFile;

	fSampleToChunkAtom = NEW QTAtom_stsc(fFile, tempTOCEntry, fDebug, fDeepDebug);
	if( fSampleToChunkAtom == NULL )
		return errInternalError;
	if( !fSampleToChunkAtom->Initialize() )
		return errInvalidQuickTimeFile;


	if( !fFile->FindTOCEntry(":mdia:minf:stbl:stsd", &tempTOCEntry, &fTOCEntry) )
		return errInvalidQuickTimeFile;

	fSampleDescriptionAtom = NEW QTAtom_stsd(fFile, tempTOCEntry, fDebug, fDeepDebug);
	if( fSampleDescriptionAtom == NULL )
		return errInternalError;
	if( !fSampleDescriptionAtom->Initialize() )
		return errInvalidQuickTimeFile;


	UInt16 offSetSize = 0;
	Bool16 coFound = false;
	
	if( fFile->FindTOCEntry(":mdia:minf:stbl:stco", &tempTOCEntry, &fTOCEntry) )
	{
		coFound = true;
		offSetSize = 4;
	} else if( fFile->FindTOCEntry(":mdia:minf:stbl:co64", &tempTOCEntry, &fTOCEntry) )
	{
		coFound = true;
		offSetSize = 8;
	}
	
	if (!coFound)
		return errInvalidQuickTimeFile;
				
	fChunkOffsetAtom = NEW QTAtom_stco(fFile, tempTOCEntry, offSetSize, fDebug, fDeepDebug);
	if( fChunkOffsetAtom == NULL )
		return errInternalError;
	if( !fChunkOffsetAtom->Initialize() )
		return errInvalidQuickTimeFile;


	if( !fFile->FindTOCEntry(":mdia:minf:stbl:stsz", &tempTOCEntry, &fTOCEntry) )
		return errInvalidQuickTimeFile;

	fSampleSizeAtom = NEW QTAtom_stsz(fFile, tempTOCEntry, fDebug, fDeepDebug);
	if( fSampleSizeAtom == NULL )
		return errInternalError;
	if( !fSampleSizeAtom->Initialize() )
		return errInvalidQuickTimeFile;
	
	if( fFile->FindTOCEntry(":mdia:minf:stbl:stss", &tempTOCEntry, &fTOCEntry) ) {
		fSyncSampleAtom = NEW QTAtom_stss(fFile, tempTOCEntry, fDebug, fDeepDebug);
		if( fSyncSampleAtom == NULL )
			return errInternalError;
		if( !fSyncSampleAtom->Initialize() )
			return errInvalidQuickTimeFile;
	} else {
		fSyncSampleAtom = NULL;
	}
	
	
	//
	// This track has been successfully initialiazed.
	fIsInitialized = true;
	return errNoError;
}



// -------------------------------------
// Sample functions
//
Bool16 QTTrack::GetSampleInfo(UInt32 SampleNumber, UInt32 * const Length, UInt64 * const Offset,  UInt32 * const SampleDescriptionIndex, QTAtom_stsc_SampleTableControlBlock * STCB)
{
	
	Assert(STCB != NULL);
	
//	printf("GetSampleInfo QTTrack SampleNumber = %ld \n", SampleNumber);

	if (STCB->fGetSampleInfo_SampleNumber == SampleNumber && STCB->fGetSampleInfo_Length > 0)
	{
//		printf("----- GetSampleInfo Cache Hit QTTrack SampleNumber = %ld \n", SampleNumber);

		if (Length) *Length = STCB->fGetSampleInfo_Length;
		if (Offset) *Offset = STCB->fGetSampleInfo_Offset;
		if (SampleDescriptionIndex) *SampleDescriptionIndex = STCB->fGetSampleInfo_SampleDescriptionIndex;

		return true;
	}




	// Temporary vars
	UInt32		sampleLength = 0;
	UInt32 		sampleDescriptionIndex = 0;
	// General vars
	UInt32		ChunkNumber, SampleOffsetInChunk;
	UInt64		sampleFileStartOffset = 0;
	UInt64		ChunkOffset = 0;
			

	// Locate this sample, compute its offset, and get its size.
	if( !SampleNumberToChunkNumber(SampleNumber, &ChunkNumber, &sampleDescriptionIndex, &SampleOffsetInChunk, STCB) )
		return false;
		

	if( !fSampleSizeAtom->SampleSize(SampleNumber, &sampleLength) )
		return false;

	if (ChunkNumber == STCB->fGetSampleInfo_LastChunk && (SampleNumber == (STCB->fGetSampleInfo_SampleNumber+1) )  )
	{
	
		sampleFileStartOffset = STCB->fGetSampleInfo_Offset;
		sampleFileStartOffset += STCB->fGetSampleInfo_Length;
		
	}
	else
	{
		if( !fChunkOffsetAtom->ChunkOffset(ChunkNumber, &ChunkOffset) )
			return false;
	
		// Walk through all of the samples previous to this one, adding up
		// their lengths to figure out what the offset from the start of
		// the chunk to this sample is.


		UInt32	tempSampleLength = fSampleSizeAtom->GetCommonSampleSize();	
		sampleFileStartOffset = ChunkOffset;

		if (tempSampleLength > 0) // samples are the same size so just multiply to get size
		{	sampleFileStartOffset +=  ( tempSampleLength * SampleOffsetInChunk );
		}
		else
		{
			for( UInt32 CurSample = (SampleNumber - SampleOffsetInChunk);CurSample < SampleNumber; CurSample++) 
			{
				// Get the length of this sample and add it to our offset.
				if( !fSampleSizeAtom->SampleSize(CurSample, &tempSampleLength) )
					return false;
				sampleFileStartOffset += tempSampleLength;			
			}
		}

		
		STCB->fGetSampleInfo_LastChunk = ChunkNumber;
		STCB->fGetSampleInfo_LastChunkOffset = ChunkOffset;
		STCB->fGetSampleInfo_SampleDescriptionIndex = sampleDescriptionIndex;
	}

	STCB->fGetSampleInfo_SampleNumber = SampleNumber;
	STCB->fGetSampleInfo_Length	= sampleLength;	
	STCB->fGetSampleInfo_Offset = sampleFileStartOffset;
	
	if (Length != NULL) *Length = sampleLength;
	if (Offset != NULL) *Offset = sampleFileStartOffset;
	if (SampleDescriptionIndex != NULL) *SampleDescriptionIndex = sampleDescriptionIndex;
	
	
	
	//
	// The sample was successfully located.
	return true;
}


Bool16 QTTrack::GetSizeOfSamplesInChunk(UInt32 chunkNumber, UInt32 * const sizePtr, UInt32 * const firstSampleNumPtr, UInt32 * const lastSampleNumPtr, QTAtom_stsc_SampleTableControlBlock * stcbPtr)
{
	UInt32 firstSample = 0;
	UInt32 lastSample = 0;
	UInt32 size = 0;
	
	if (stcbPtr && stcbPtr->fGetSizeOfSamplesInChunk_chunkNumber == chunkNumber)
	{
//		printf("QTTrack::GetSizeOfSamplesInChunk cache hit %ld \n", chunkNumber);
		if (firstSampleNumPtr != NULL) *firstSampleNumPtr = stcbPtr->fGetSizeOfSamplesInChunk_firstSample;
		if (lastSampleNumPtr !=  NULL) *lastSampleNumPtr = stcbPtr->fGetSizeOfSamplesInChunk_lastSample;
		if (sizePtr != NULL) *sizePtr = stcbPtr->fGetSizeOfSamplesInChunk_size;
		return true;
	}	
	
	Bool16 result = GetChunkFirstLastSample(chunkNumber, &firstSample, &lastSample, stcbPtr);
	
	if (true == result && (sizePtr != NULL) )
	{	
		result = SampleRangeSize(firstSample, lastSample, &size);
	}	
	
	if (firstSampleNumPtr != NULL) *firstSampleNumPtr = firstSample;
	if (lastSampleNumPtr !=  NULL) *lastSampleNumPtr = lastSample;
	if (sizePtr != NULL) *sizePtr = size;

	if (stcbPtr && true == result)
	{	
		stcbPtr->fGetSizeOfSamplesInChunk_chunkNumber = chunkNumber;
		stcbPtr->fGetSizeOfSamplesInChunk_firstSample = firstSample;
		stcbPtr->fGetSizeOfSamplesInChunk_lastSample = lastSample;
		stcbPtr->fGetSizeOfSamplesInChunk_size = size;
	}
	
	return result;
}


Bool16 QTTrack::GetSample(UInt32 SampleNumber, char * Buffer, UInt32 * Length, QTFile_FileControlBlock * FCB, QTAtom_stsc_SampleTableControlBlock * STCB)
{
	// General vars
	UInt32		SampleDescriptionIndex;
	UInt64		SampleOffset;
	
	//
	// Get the location and size of this sample.
	if( !this->GetSampleInfo(SampleNumber, Length, &SampleOffset, &SampleDescriptionIndex, STCB) )
		return false;
	
	//
	// Read in the sample
	if( !fDataReferenceAtom->Read(SampleDescriptionIndex, SampleOffset, Buffer, *Length, FCB) )
		return false;
	
	//
	// The sample was successfully read in.
	return true;
}



// -------------------------------------
// Debugging functions
//
void QTTrack::DumpTrack(void)
{
	//
	// Dump this track's information.
	DEBUG_PRINT(("QTTrack::DumpTrack - Dumping track.\n"));
	DEBUG_PRINT(("QTTrack::DumpTrack - ..Track name: \"%s\".\n", fTrackName ? fTrackName : "<untitled>"));

	//
	// Dump the sub-atoms of this track.
	if( fTrackHeaderAtom != NULL )
		fTrackHeaderAtom->DumpAtom();

	if( fMediaHeaderAtom != NULL )
		fMediaHeaderAtom->DumpAtom();
	
	if( fDataReferenceAtom != NULL )
		fDataReferenceAtom->DumpAtom();
	
	if( fTimeToSampleAtom != NULL )
		fTimeToSampleAtom->DumpAtom();
	if( fSampleToChunkAtom != NULL )
		fSampleToChunkAtom->DumpAtom();
	if( fSampleDescriptionAtom != NULL )
		fSampleDescriptionAtom->DumpAtom();
	if( fChunkOffsetAtom != NULL )
		fChunkOffsetAtom->DumpAtom();
	if( fSampleSizeAtom != NULL )
		fSampleSizeAtom->DumpAtom();
}
