/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005 
 *					All rights reserved
 *
 *  This file is part of GPAC / ISO Media File Format sub-project
 *
 *  GPAC 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 2, or (at your option)
 *  any later version.
 *   
 *  GPAC 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */

#include <gpac/intern/m4_isomedia_dev.h>

#ifndef M4_READ_ONLY

#define MP4_CPRT_NOTICE "IsoMedia File Produced with GPAC"

static M4Err M4_InsertCopyright(M4File *movie)
{
	u32 i;
	Atom *a;
	FreeSpaceAtom *_free;
	for (i = 0; i < ChainGetCount(movie->TopAtoms); i++) {
		a = ChainGetEntry(movie->TopAtoms, i);
		if (a->type == FreeSpaceAtomType) {
			_free = (FreeSpaceAtom *)a;
			if (_free->dataSize) {
				if (!strcmp(_free->data, MP4_CPRT_NOTICE)) return M4OK;
				if (strstr(_free->data, "File Produced with GPAC")) {
					free(_free->data);
					_free->data = strdup(MP4_CPRT_NOTICE);
					_free->dataSize = strlen(_free->data);
					return M4OK;
				}
			}
		}
	}
	a = CreateAtom(FreeSpaceAtomType);
	if (!a) return M4OutOfMem;
	_free = (FreeSpaceAtom *)a;
	_free->dataSize = strlen(MP4_CPRT_NOTICE) + 1;
	_free->data = malloc(_free->dataSize);
	if (!_free->data) return M4OutOfMem;
	strcpy(_free->data, MP4_CPRT_NOTICE);
	return ChainAddEntry(movie->TopAtoms, _free);
}

typedef struct 
{
	/*the curent sample of this track*/
	u32 sampleNumber;
	/*timeScale of the media (for interleaving)*/
	u32 timeScale;
	/*this is for generic, time-based interleaving. Expressed in Media TimeScale*/
	u32 chunkDur;
	u32 DTSprev;
	u8 isDone;
	MediaAtom *mdia;
	/*each writer has a sampleToChunck and ChunkOffset tables
	these tables are filled during emulation mode and then will	replace the table in the SampleTableAtom*/
	SampleToChunkAtom *stsc;
	/*we don't know if it's a large offset or not*/
	Atom *stco;
} TrackWriter;

typedef struct
{
	char *buffer;
	u32 size;
	M4File *movie;
	u32 total_samples, nb_done;
	void (*progress)(void *cbk, u32 done, u32 total);
	void *cbck;
} MovieWriter;

void CleanWriters(Chain *writers)
{
	TrackWriter *writer;
	while (ChainGetCount(writers)) {
		writer = (TrackWriter*)ChainGetEntry(writers, 0);
		DelAtom(writer->stco);
		DelAtom((Atom *)writer->stsc);
		free(writer);
		ChainDeleteEntry(writers, 0);
	}
}

void ResetWriters(Chain *writers)
{
	u32 i;
	TrackWriter *writer;
	for (i=0; i<ChainGetCount(writers); i++) {
		writer = (TrackWriter*)ChainGetEntry(writers, i);
		writer->isDone = 0;
		writer->chunkDur = 0;
		writer->DTSprev = 0;
		writer->sampleNumber = 1;
		DelAtom((Atom *)writer->stsc);
		writer->stsc = (SampleToChunkAtom *) CreateAtom(SampleToChunkAtomType);
		if (writer->stco->type == ChunkOffsetAtomType) {
			free(((ChunkOffsetAtom *)writer->stco)->offsets);
			((ChunkOffsetAtom *)writer->stco)->offsets = NULL;
			((ChunkOffsetAtom *)writer->stco)->entryCount = 0;
		} else {
			free(((ChunkLargeOffsetAtom *)writer->stco)->offsets);
			((ChunkLargeOffsetAtom *)writer->stco)->offsets = NULL;
			((ChunkLargeOffsetAtom *)writer->stco)->entryCount = 0;
		}
	}
}

M4Err SetupWriters(MovieWriter *mw, Chain *writers, u8 interleaving)
{
	u32 i, trackCount;
	TrackWriter *writer;
	TrackAtom *trak;
	M4File *movie = mw->movie;

	mw->total_samples = mw->nb_done = 0;
	if (!movie->moov) return M4OK;

	trackCount = ChainGetCount(movie->moov->trackList);
	for (i = 0; i < trackCount; i++) {
		trak = GetTrack(movie->moov, i+1);
		
		writer = (TrackWriter*)malloc(sizeof(TrackWriter));
		if (!writer) goto exit;
		writer->sampleNumber = 1;
		writer->mdia = trak->Media;
		writer->timeScale = trak->Media->mediaHeader->timeScale;
		writer->isDone = 0;
		writer->DTSprev = 0;
		writer->chunkDur = 0;
		writer->stsc = (SampleToChunkAtom *) CreateAtom(SampleToChunkAtomType);
		if (trak->Media->information->sampleTable->ChunkOffset->type == ChunkOffsetAtomType) {
			writer->stco = CreateAtom(ChunkOffsetAtomType);
		} else {
			writer->stco = CreateAtom(ChunkLargeOffsetAtomType);
		}
		/*stops from chunk escape*/
		if (interleaving) writer->mdia->information->sampleTable->MaxSamplePerChunk = 0;
		/*for progress, assume only one descIndex*/
		if (Media_IsSelfContained(writer->mdia, 1)) mw->total_samples += trak->Media->information->sampleTable->SampleSize->sampleCount;
		/*optimization for interleaving: put audio last (this can be overriden by priorities)*/
		if (movie->storageMode != M4_INTERLEAVED) {
			ChainAddEntry(writers, writer);	
		} else {
			if (writer->mdia->information->InfoHeader->type == SoundMediaHeaderAtomType) {
				ChainAddEntry(writers, writer);	
			} else {
				ChainInsertEntry(writers, writer, 0);	
			}
		}
	}
	return M4OK;

exit:
	CleanWriters(writers);
	return M4OutOfMem;
}


static void ShiftMetaOffset(MetaAtom *meta, u64 offset)
{
	u32 i, count;
	if (!meta->item_locations) return;

	count = ChainGetCount(meta->item_locations->location_entries);
	for (i=0; i<count; i++) {
		ItemLocationEntry *iloc = ChainGetEntry(meta->item_locations->location_entries, i);
		if (!iloc->base_offset) {
			ItemExtentEntry *entry = ChainGetEntry(iloc->extent_entries, 0);
			if (entry && !entry->extent_length && !entry->original_extent_offset && (ChainGetCount(iloc->extent_entries)==1) )
				continue;
		}

		iloc->base_offset += offset;
	}
}

static M4Err ShiftOffset(M4File *file, Chain *writers, u64 offset)
{
	u32 i, j, k, l, last;
	TrackWriter *writer;
	stscEntry *ent;
	ChunkOffsetAtom *stco;
	ChunkLargeOffsetAtom *co64;

	if (file->meta) ShiftMetaOffset(file->meta, offset);
	if (file->moov && file->moov->meta) ShiftMetaOffset(file->moov->meta, offset);

	for (i = 0; i < ChainGetCount(writers); i++) {
		writer = (TrackWriter*)ChainGetEntry(writers, i);
		if (writer->mdia->mediaTrack->meta) ShiftMetaOffset(writer->mdia->mediaTrack->meta, offset);

		//we have to proceed entry by entry in case a part of the media is not self-contained...
		for (j = 0; j<ChainGetCount(writer->stsc->entryList); j++) {
			ent = (stscEntry*)ChainGetEntry(writer->stsc->entryList, j);
			if (!Media_IsSelfContained(writer->mdia, ent->sampleDescriptionIndex)) continue;

			//OK, get the chunk(s) number(s) and "shift" its (their) offset(s).
			if (writer->stco->type == ChunkOffsetAtomType) {
				stco = (ChunkOffsetAtom *) writer->stco;
				//be carefull for the last entry, nextChunk is set to 0 in edit mode...
				last = ent->nextChunk ? ent->nextChunk : stco->entryCount + 1;
				for (k = ent->firstChunk; k < last; k++) {

					if (stco->offsets[k-1] + offset > 0xFFFFFFFF) {
						//too bad, rewrite the table....
						co64 = (ChunkLargeOffsetAtom *) CreateAtom(ChunkLargeOffsetAtomType);
						if (!co64) return M4OutOfMem;
						co64->entryCount = stco->entryCount;
						co64->offsets = (u64*)malloc(co64->entryCount * sizeof(u64));
						if (!co64) {
							DelAtom((Atom *)co64);
							return M4OutOfMem;
						}
						//duplicate the table 
						for (l = 0; l < co64->entryCount; l++) {
							co64->offsets[l] = (u64) stco->offsets[l];
							if (l + 1 == k) co64->offsets[l] += offset;
						}
						//and replace our atom
						DelAtom(writer->stco);
						writer->stco = (Atom *)co64;
					} else {
						stco->offsets[k-1] += (u32) offset;
					}
				}
			} else {
				co64 = (ChunkLargeOffsetAtom *) writer->stco;
				//be carefull for the last entry ...
				last = ent->nextChunk ? ent->nextChunk : co64->entryCount + 1;
				for (k = ent->firstChunk; k < last; k++) {
					co64->offsets[k-1] += offset;
				}
			}
		}
	}
	
	return M4OK;

}


//replace the chunk and offset tables...
static M4Err WriteMoovAndMeta(M4File *movie, Chain *writers, BitStream *bs)
{
	u32 i;
	TrackWriter *writer;
	M4Err e;
	Atom *stco;
	SampleToChunkAtom *stsc;

	if (movie->meta) {
		//write the moov atom...
		e = SizeAtom((Atom *)movie->meta);
		if (e) return e;
		e = WriteAtom((Atom *)movie->meta, bs);
		if (e) return e;
	}

	if (movie->moov) {
		//switch all our tables
		for (i = 0; i < ChainGetCount(writers); i++) {
			writer = (TrackWriter*)ChainGetEntry(writers, i);
			//don't delete them !!!
			stsc = writer->mdia->information->sampleTable->SampleToChunk;
			stco = writer->mdia->information->sampleTable->ChunkOffset;
			writer->mdia->information->sampleTable->SampleToChunk = writer->stsc;
			writer->mdia->information->sampleTable->ChunkOffset = writer->stco;
			writer->stco = stco;
			writer->stsc = stsc;
		}
		//write the moov atom...
		e = SizeAtom((Atom *)movie->moov);
		if (e) return e;
		e = WriteAtom((Atom *)movie->moov, bs);
		//and re-switch our table. We have to do it that way because it is 
		//needed when the moov is written first
		for (i = 0; i < ChainGetCount(writers); i++) {
			writer = (TrackWriter*)ChainGetEntry(writers, i);
			//don't delete them !!!
			stsc = writer->stsc;
			stco = writer->stco;
			writer->stsc = writer->mdia->information->sampleTable->SampleToChunk;
			writer->stco = writer->mdia->information->sampleTable->ChunkOffset;
			writer->mdia->information->sampleTable->SampleToChunk = stsc;
			writer->mdia->information->sampleTable->ChunkOffset = stco;
		}
		if (e) return e;
	}
	return M4OK;
}

//compute the size of the moov as it will be written.
u64 GetMoovAndMetaSize(M4File *movie, Chain *writers)
{
	u32 i;
	u64 size;
	TrackWriter *writer;

	size = 0;
	if (movie->moov) {
		SizeAtom((Atom *)movie->moov);
		size = movie->moov->size;
		if (size > 0xFFFFFFFF) size += 8;

		for (i=0; i < ChainGetCount(writers); i++) {
			writer = (TrackWriter*)ChainGetEntry(writers, i);
			size -= writer->mdia->information->sampleTable->ChunkOffset->size;
			size -= writer->mdia->information->sampleTable->SampleToChunk->size;
			SizeAtom((Atom *)writer->stsc);
			SizeAtom(writer->stco);
			size += writer->stsc->size;
			size += writer->stco->size;
		}
	}
	if (movie->meta) {
		u64 msize;
		SizeAtom((Atom *)movie->meta);
		msize = movie->meta->size;
		if (msize > 0xFFFFFFFF) msize += 8;
		size += msize;
	}
	return size;
}

//Write a sample to the file - this is only called for self-contained media
M4Err WriteSample(MovieWriter *mw, u32 size, u64 offset, u8 isEdited, BitStream *bs)
{
	DataMap *map;
	u32 bytes;

	if (!mw->buffer) {
		mw->buffer = malloc(size);
		mw->size = size;
	} else if (size>mw->size) {
		mw->buffer = realloc(mw->buffer, size);
		mw->size = size;
	}

	if (!mw->buffer) return M4OutOfMem;

	if (isEdited) {
		map = mw->movie->editFileMap;
	} else {
		map = mw->movie->movieFileMap;
	}
	//get the payload...
	bytes = DataMap_GetData(map, mw->buffer, size, offset);
	if (bytes != size) return M4IOErr;
	//write it to our stream...
	bytes = BS_WriteData(bs, (unsigned char*)mw->buffer, size);
	if (bytes != size) return M4IOErr;

	if (mw->progress) {
		mw->nb_done++;
		mw->progress(mw->cbck, mw->nb_done, mw->total_samples);
	}
	return M4OK;
}


M4Err DoWriteMeta(M4File *file, MetaAtom *meta, BitStream *bs, Bool Emulation, u64 baseOffset, u64 *mdatSize)
{
	ItemExtentEntry *entry;
	u64 maxExtendOffset, maxExtendSize;
	u32 i, j, count;

	maxExtendOffset = 0;
	maxExtendSize = 0;
	*mdatSize = 0;
	if (!meta->item_locations) return M4OK;

	count = ChainGetCount(meta->item_locations->location_entries);
	for (i=0; i<count; i++) {
		u64 it_size;
		ItemLocationEntry *iloc = ChainGetEntry(meta->item_locations->location_entries, i);
		/*get item info*/
		ItemInfoEntryAtom *iinf = NULL;
		for (j=0; j<ChainGetCount(meta->item_infos->item_infos); j++) {
			iinf = ChainGetEntry(meta->item_infos->item_infos, j);
			if (iinf->item_ID==iloc->item_ID) break;
			iinf = NULL;
		}

		if (!iloc->base_offset && (ChainGetCount(iloc->extent_entries)==1)) {
			entry = ChainGetEntry(iloc->extent_entries, 0);
			if (!entry->extent_length && !entry->original_extent_offset) {
				entry->extent_offset = 0;
				continue;
			}
		}

		iloc->base_offset = baseOffset;

		/*for self contained only*/
		if (!iloc->data_reference_index) {
			/*new resource*/
			if (iinf->full_path) {
				FILE *src = f64_open(iinf->full_path, "rb");

				if (!src) continue;
				f64_seek(src, 0, SEEK_END);
				it_size = f64_tell(src);
				f64_seek(src, 0, SEEK_SET);
				if (maxExtendSize<it_size) maxExtendSize = it_size;

				if (!ChainGetCount(iloc->extent_entries)) {
					SAFEALLOC(entry, sizeof(ItemExtentEntry));
					ChainAddEntry(iloc->extent_entries, entry);
				}
				entry = ChainGetEntry(iloc->extent_entries, 0);
				entry->extent_offset = 0;
				entry->extent_length = it_size;

				/*OK write to mdat*/
				if (!Emulation) {
					char cache_data[4096];
					u64 remain = entry->extent_length;
					while (remain) {
						u32 size_cache = (remain>4096) ? 4096 : (u32) remain;
						fread(cache_data, 1, size_cache, src);
						BS_WriteData(bs, cache_data, size_cache);
						remain -= size_cache;
					}
				}
				fclose(src);
			} 
			else if (ChainGetCount(iloc->extent_entries)) {
				u32 j;
				it_size = 0;
				for (j=0; j<ChainGetCount(iloc->extent_entries); j++) {
					entry = ChainGetEntry(iloc->extent_entries, j);
					if (j && (maxExtendOffset<it_size) ) maxExtendOffset = it_size;
					/*compute new offset*/
					entry->extent_offset = baseOffset + it_size;

					it_size += entry->extent_length;
					if (maxExtendSize<entry->extent_length) maxExtendSize = entry->extent_length;

					if (!Emulation) {
						char cache_data[4096];
						u64 remain = entry->extent_length;
						while (remain) {
							u32 size_cache = (remain>4096) ? 4096 : (u32) remain;
							/*Reading from the input file*/
							BS_Seek(file->movieFileMap->bs, entry->original_extent_offset + iloc->original_base_offset);
							BS_ReadData(file->movieFileMap->bs, cache_data, size_cache);
							/*Writing to the output file*/
							BS_WriteData(bs, cache_data, size_cache);
							remain -= size_cache;
						}
					}
				}
			}
			baseOffset += it_size;
			*mdatSize += it_size;
		}
	}
	/*update offset & size length fields*/
	if (baseOffset>0xFFFFFFFF) meta->item_locations->base_offset_size = 8;
	else if (baseOffset) meta->item_locations->base_offset_size = 4;
	
	if (maxExtendSize>0xFFFFFFFF) meta->item_locations->length_size = 8;
	else if (maxExtendSize) meta->item_locations->length_size = 4;

	if (maxExtendOffset>0xFFFFFFFF) meta->item_locations->offset_size = 8;
	else if (maxExtendOffset) meta->item_locations->offset_size = 4;
	return M4OK;
}

//this function writes track by track in the order of tracks inside the moov...
M4Err DoWrite(MovieWriter *mw, Chain *writers, BitStream *bs, u8 Emulation, u64 StartOffset)
{
	u32 i;
	M4Err e;
	TrackWriter *writer;
	u64 offset, sampOffset, predOffset;
	u32 chunkNumber, descIndex, sampSize;
	u8 isEdited, force;
	u64 size, mdatSize = 0;
	M4File *movie = mw->movie;

	/*write meta content first - WE DON'T support fragmentation of resources in ISOM atm*/
	if (movie->openMode != M4_OPEN_WRITE) {
		if (movie->meta) {
			e = DoWriteMeta(movie, movie->meta, bs, Emulation, StartOffset, &size);
			if (e) return e;
			mdatSize += size;
			StartOffset += size;
		}
		if (movie->moov && movie->moov->meta) {
			e = DoWriteMeta(movie, movie->meta, bs, Emulation, StartOffset, &size);
			if (e) return e;
			mdatSize += size;
			StartOffset += size;
		}
		for (i = 0; i < ChainGetCount(writers); i++) {
			writer = (TrackWriter*)ChainGetEntry(writers, i);
			if (writer->mdia->mediaTrack->meta) {
				e = DoWriteMeta(movie, movie->meta, bs, Emulation, StartOffset, &size);
				if (e) return e;
				mdatSize += size;
				StartOffset += size;
			}
		}
	}

	offset = StartOffset;
	predOffset = 0;
	for (i = 0; i < ChainGetCount(writers); i++) {
		writer = (TrackWriter*)ChainGetEntry(writers, i);
		while (!writer->isDone) {
			//To Check: are empty sample tables allowed ???
			if (writer->sampleNumber > writer->mdia->information->sampleTable->SampleSize->sampleCount) {
				writer->isDone = 1;
				continue;
			}
			e = stbl_GetSampleInfos(writer->mdia->information->sampleTable, writer->sampleNumber, &sampOffset, &chunkNumber, &descIndex, &isEdited);
			if (e) return e;
			e = stbl_GetSampleSize(writer->mdia->information->sampleTable->SampleSize, writer->sampleNumber, &sampSize);
			if (e) return e;

			//update our chunks. 
			force = 0;
			if (movie->openMode == M4_OPEN_WRITE) {
				offset = sampOffset;
				if (predOffset != offset) 
					force = 1;
			}
			//update our global offset...
			if (Media_IsSelfContained(writer->mdia, descIndex) ) {
				e = stbl_SetChunkAndOffset(writer->mdia->information->sampleTable, writer->sampleNumber, descIndex, writer->stsc, &writer->stco, offset, force);
				if (e) return e;
				if (movie->openMode == M4_OPEN_WRITE) {
					predOffset = sampOffset + sampSize;
				} else {
					offset += sampSize;
					mdatSize += sampSize;
				}
			} else {
				if (predOffset != offset) force = 1;
				predOffset = sampOffset + sampSize;
				//we have a DataRef, so use the offset idicated in sampleToChunk and ChunkOffset tables...
				e = stbl_SetChunkAndOffset(writer->mdia->information->sampleTable, writer->sampleNumber, descIndex, writer->stsc, &writer->stco, sampOffset, force);
				if (e) return e;
			}
			//we write the sample if not emulation
			if (!Emulation) {
				if (Media_IsSelfContained(writer->mdia, descIndex) ) {
					e = WriteSample(mw, sampSize, sampOffset, isEdited, bs);
					if (e) return e;
				}
			}
			//ok, the track is done
			if (writer->sampleNumber == writer->mdia->information->sampleTable->SampleSize->sampleCount) {
				writer->isDone = 1;
			} else {
				writer->sampleNumber ++;
			}
		}
	}
	//set the mdatSize...
	movie->mdat->dataSize = mdatSize;
	return M4OK;
}


//write the file track by track, with moov atom before or after the mdat
M4Err WriteFlat(MovieWriter *mw, u8 moovFirst, BitStream *bs)
{
	M4Err e;
	u32 i;
	u64 offset, finalOffset, totSize, begin, firstSize, finalSize;
	Atom *a;
	Chain *writers = NewChain();
	M4File *movie = mw->movie;

	begin = totSize = 0;

	//first setup the writers
	e = SetupWriters(mw, writers, 0);
	if (e) goto exit;

	if (!moovFirst) {
		if (movie->openMode == M4_OPEN_WRITE) {
			begin = 0;
			totSize = DataMap_GetTotalOffset(movie->editFileMap);
			/*starts atoms have not been written yet, do it*/
			if (!totSize) {
				if (movie->brand) {
					e = SizeAtom((Atom *)movie->brand); if (e) goto exit;
					e = WriteAtom((Atom *)movie->brand, movie->editFileMap->bs); if (e) goto exit;
					totSize += movie->brand->size;
					begin += movie->brand->size;
				}
				if (movie->pdin) {
					e = SizeAtom((Atom *)movie->pdin); if (e) goto exit;
					e = WriteAtom((Atom *)movie->pdin, movie->editFileMap->bs); if (e) goto exit;
					totSize += movie->pdin->size;
					begin += movie->pdin->size;
				}
			} else {
				if (movie->brand) begin += movie->brand->size;
				if (movie->pdin) begin += movie->pdin->size;
			}
			totSize -= begin;
		} else {
			//the brand atom is always first no matter what
			if (movie->brand) {
				e = SizeAtom((Atom *)movie->brand);
				if (e) goto exit;
				e = WriteAtom((Atom *)movie->brand, bs);
				if (e) goto exit;
			}
			/*then progressive download*/
			if (movie->pdin) {
				e = SizeAtom((Atom *)movie->pdin);
				if (e) goto exit;
				e = WriteAtom((Atom *)movie->pdin, bs);
				if (e) goto exit;
			}
		}

		//if the moov is at the end, write directly
		for (i=0; i<ChainGetCount(movie->TopAtoms); i++) {
			a = (Atom*)ChainGetEntry(movie->TopAtoms, i);
			switch (a->type) {
			/*atoms written by hand*/
			case MovieAtomType:
			case MetaAtomType:
			case FileTypeAtomType:
			case ProgressiveDownloadAtomType:
				break;
			case MediaDataAtomType:
				//in case we're capturing
				if (movie->openMode == M4_OPEN_WRITE) {
					//emulate a write to recreate our tables (media data already written)
					e = DoWrite(mw, writers, bs, 1, begin);
					if (e) goto exit;
					continue;
				}
				//to avoid computing the size each time write always 4 + 4 + 8 bytes before
				begin = BS_GetPosition(bs);
				BS_WriteU64(bs, 0);
				BS_WriteU64(bs, 0);
				e = DoWrite(mw, writers, bs, 0, BS_GetPosition(bs));
				if (e) goto exit;
				totSize = BS_GetPosition(bs) - begin;
				break;
			default:
				e = SizeAtom(a);
				if (e) goto exit;
				e = WriteAtom(a, bs);
				if (e) goto exit;
				break;
			}
		}

		//OK, write the movie atom.
		e = WriteMoovAndMeta(movie, writers, bs);
		if (e) goto exit;

		/*if data has been written, update mdat size*/
		if (totSize) {
			offset = BS_GetPosition(bs);
			e = BS_Seek(bs, begin);
			if (e) goto exit;
			if (totSize > 0xFFFFFFFF) {
				BS_WriteU32(bs, 1);
			} else {
				BS_WriteU32(bs, (u32) totSize);
			}
			BS_WriteU32(bs, MediaDataAtomType);
			if (totSize > 0xFFFFFFFF) BS_WriteU64(bs, totSize);
			e = BS_Seek(bs, offset);
		}
		movie->mdat->size = totSize;
		goto exit;
	}

	//nope, we have to write the moov first. The pb is that 
	//1 - we don't know its size till the mdat is written
	//2 - we don't know the ofset at which the mdat will start...
	//3 - once the mdat is written, the chunkOffset table can have changed...
	
	//the brand atom is always first no matter what
	if (movie->brand) {
		e = SizeAtom((Atom *)movie->brand);
		if (e) goto exit;
		e = WriteAtom((Atom *)movie->brand, bs);
		if (e) goto exit;
	}
	/*then progressive dnload*/
	if (movie->pdin) {
		e = SizeAtom((Atom *)movie->pdin);
		if (e) goto exit;
		e = WriteAtom((Atom *)movie->pdin, bs);
		if (e) goto exit;
	}
	//What we will do is first emulate the write from the begining...
	//note: this will set the size of the mdat
	e = DoWrite(mw, writers, bs, 1, BS_GetPosition(bs));
	if (e) goto exit;
	
	firstSize = GetMoovAndMetaSize(movie, writers);
	//offset = (firstSize > 0xFFFFFFFF ? firstSize + 8 : firstSize) + 8 + (movie->mdat->dataSize > 0xFFFFFFFF ? 8 : 0);
	offset = firstSize + 8 + (movie->mdat->dataSize > 0xFFFFFFFF ? 8 : 0);
	e = ShiftOffset(movie, writers, offset);
	if (e) goto exit;
	//get the size and see if it has changed (eg, we moved to 64 bit offsets)
	finalSize = GetMoovAndMetaSize(movie, writers);
	if (firstSize != finalSize) {
		//we need to remove our offsets
		ResetWriters(writers);
		//finalOffset = (finalSize > 0xFFFFFFFF ? finalSize + 8 : finalSize) + 8 + (movie->mdat->dataSize > 0xFFFFFFFF ? 8 : 0);
		finalOffset = finalSize + 8 + (movie->mdat->dataSize > 0xFFFFFFFF ? 8 : 0);
		//OK, now we're sure about the final size.
		//we don't need to re-emulate, as the only thing that changed is the offset
		//so just shift the offset
		e = ShiftOffset(movie, writers, finalOffset - offset);
		if (e) goto exit;
	}
	//now write our stuff
	e = WriteMoovAndMeta(movie, writers, bs);
	if (e) goto exit;
	e = SizeAtom((Atom *)movie->mdat);
	if (e) goto exit;
	e = WriteAtom((Atom *)movie->mdat, bs);
	if (e) goto exit;

	//we don't need the offset as the moov is already written...
	ResetWriters(writers);
	e = DoWrite(mw, writers, bs, 0, 0);
	if (e) goto exit;
	//then the rest
	for (i = 0; i < ChainGetCount(movie->TopAtoms); i++) {
		a = (Atom*)ChainGetEntry(movie->TopAtoms, i);
		switch (a->type) {
		case MovieAtomType:
		case MetaAtomType:
		case FileTypeAtomType:
		case ProgressiveDownloadAtomType:
		case MediaDataAtomType:
			break;
		default:
			e = SizeAtom(a);
			if (e) goto exit;
			e = WriteAtom(a, bs);
			if (e) goto exit;
		}
	}

exit:
	CleanWriters(writers);
	DeleteChain(writers);
	return e;
}

M4Err DoFullInterleave(MovieWriter *mw, Chain *writers, BitStream *bs, u8 Emulation, u32 StartOffset)
{

	u32 i, tracksDone;
	TrackWriter *tmp, *curWriter, *prevWriter;
	M4Err e;
	u32 DTS, descIndex, sampSize, chunkNumber, DTStmp, TStmp;
	s32 res;
	u16 curGroupID, curTrackPriority;
	u8 forceNewChunk, writeGroup, isEdited;
	//this is used to emulate the write ...
	u64 offset, totSize, sampOffset;
	M4File *movie = mw->movie;
	e = M4OK;


	totSize = 0;
	curGroupID = 1;

	prevWriter = NULL;
	//we emulate a write from this offset...
	offset = StartOffset;
	writeGroup = 1;
	tracksDone = 0;

	
	//browse each groups
	while (1) {
		writeGroup = 1;

		//proceed a group
		while (writeGroup) {
			//first get the appropriated sample for the min time in this group
			curWriter = NULL;
			DTStmp = -1;
			TStmp = 0;
			curTrackPriority = -1;

			for (i = 0; i < ChainGetCount(writers); i++) {
				tmp = ChainGetEntry(writers, i);

				//is it done writing ?
				//is it in our group ??
				if (tmp->isDone || tmp->mdia->information->sampleTable->groupID != curGroupID) continue;

				//OK, get the current sample in this track
				stbl_GetSampleDTS(tmp->mdia->information->sampleTable->TimeToSample, tmp->sampleNumber, &DTS);				
				res = TStmp ? DTStmp * tmp->timeScale - DTS * TStmp : 0;
				if (res < 0) continue;
				if ((!res) && curTrackPriority <= tmp->mdia->information->sampleTable->trackPriority) continue;
				curWriter = tmp;
				curTrackPriority = tmp->mdia->information->sampleTable->trackPriority;
				DTStmp = DTS;
				TStmp = tmp->timeScale;
			}
			//no sample found, we're done with this group
			if (!curWriter) {
				//we're done with the group
				curTrackPriority = 0;
				writeGroup = 0;
				continue;
			}
			//To Check: are empty sample tables allowed ???
			if (curWriter->sampleNumber > curWriter->mdia->information->sampleTable->SampleSize->sampleCount) {
				curWriter->isDone = 1;
				tracksDone ++;
				continue;
			}

			e = stbl_GetSampleInfos(curWriter->mdia->information->sampleTable, curWriter->sampleNumber, &sampOffset, &chunkNumber, &descIndex, &isEdited);
			if (e) return e;
			e = stbl_GetSampleSize(curWriter->mdia->information->sampleTable->SampleSize, curWriter->sampleNumber, &sampSize);
			if (e) return e;
			
			//do we actually write, or do we emulate ?
			if (Emulation) {
				//are we in the same track ??? If not, force a new chunk when adding this sample
				if (curWriter != prevWriter) {
					forceNewChunk = 1;
				} else {
					forceNewChunk = 0;
				}
				//update our offsets...
				if (Media_IsSelfContained(curWriter->mdia, descIndex) ) {
					e = stbl_SetChunkAndOffset(curWriter->mdia->information->sampleTable, curWriter->sampleNumber, descIndex, curWriter->stsc, &curWriter->stco, offset, forceNewChunk);
					if (e) return e;
					offset += sampSize;
					totSize += sampSize;
				} else {
					//we have a DataRef, so use the offset idicated in sampleToChunk 
					//and ChunkOffset tables...
					e = stbl_SetChunkAndOffset(curWriter->mdia->information->sampleTable, curWriter->sampleNumber, descIndex, curWriter->stsc, &curWriter->stco, sampOffset, 0);
					if (e) return e;
				}
			} else {
				//this is no game, we're writing ....
				if (Media_IsSelfContained(curWriter->mdia, descIndex) ) {
					e = WriteSample(mw, sampSize, sampOffset, isEdited, bs);
					if (e) return e;
				}
			}
			//ok, the sample is done
			if (curWriter->sampleNumber == curWriter->mdia->information->sampleTable->SampleSize->sampleCount) {
				curWriter->isDone = 1;
				//one more track done...
				tracksDone ++;
			} else {
				curWriter->sampleNumber ++;
			}
			prevWriter = curWriter;
		}
		//if all our track are done, break
		if (tracksDone == ChainGetCount(writers)) break;
		//go to next group
		curGroupID ++;
	}
	movie->mdat->dataSize = totSize;
	return M4OK;
}


M4Err DoInterleave(MovieWriter *mw, Chain *writers, BitStream *bs, u8 Emulation, u32 StartOffset)
{
	u32 i, tracksDone;
	TrackWriter *tmp, *curWriter;
	M4Err e;
	u32 DTS, descIndex, sampSize, chunkNumber;
	u16 curGroupID;
	u8 forceNewChunk, writeGroup, isEdited;
	//this is used to emulate the write ...
	u64 offset, sampOffset, size, mdatSize;
	u32 count;
	M4File *movie = mw->movie;

	mdatSize = 0;

	/*write meta content first - WE DON'T support fragmentation of resources in ISOM atm*/
	if (movie->meta) {
		e = DoWriteMeta(movie, movie->meta, bs, Emulation, StartOffset, &size);
		if (e) return e;
		mdatSize += size;
		StartOffset += (u32) size;
	}
	if (movie->moov && movie->moov->meta) {
		e = DoWriteMeta(movie, movie->moov->meta, bs, Emulation, StartOffset, &size);
		if (e) return e;
		mdatSize += size;
		StartOffset += (u32) size;
	}
	for (i = 0; i < ChainGetCount(writers); i++) {
		TrackWriter *writer = (TrackWriter*)ChainGetEntry(writers, i);
		if (writer->mdia->mediaTrack->meta) {
			e = DoWriteMeta(movie, writer->mdia->mediaTrack->meta, bs, Emulation, StartOffset, &size);
			if (e) return e;
			mdatSize += size;
			StartOffset += (u32) size;
		}
	}



	if (movie->storageMode == M4_FULL_INTERLEAVED) 
		return DoFullInterleave(mw, writers, bs, Emulation, StartOffset);

	e = M4OK;

	curGroupID = 1;
	//we emulate a write from this offset...
	offset = StartOffset;
	writeGroup = 1;
	tracksDone = 0;

	count = ChainGetCount(writers);
	//browse each groups
	while (1) {
		writeGroup = 1;

		//proceed a group
		while (writeGroup) {
			curWriter = NULL;
			for (i=0 ; i < count; i++) {
				tmp = (TrackWriter*)ChainGetEntry(writers, i);
				
				//is it done writing ?
				if (tmp->isDone) continue;

				//is it in our group ??
				if (tmp->mdia->information->sampleTable->groupID != curGroupID) continue;

				//write till this chunk is full on this track...
				while (1) {
					//To Check: are empty sample tables allowed ???
					if (tmp->sampleNumber > tmp->mdia->information->sampleTable->SampleSize->sampleCount) {
						tmp->isDone = 1;
						tracksDone ++;
						break;
					}

					//OK, get the current sample in this track
					stbl_GetSampleDTS(tmp->mdia->information->sampleTable->TimeToSample, tmp->sampleNumber, &DTS);

					//can this samle fit in our chunk ?
					if ( ( (DTS - tmp->DTSprev) + tmp->chunkDur) *  movie->moov->mvhd->timeScale > movie->interleavingTime * tmp->timeScale) {
						//in case the sample is longer than InterleaveTime
						if (!tmp->chunkDur) {
							forceNewChunk = 1;
						} else {
							//this one is full. go to next one (exit the loop)
							tmp->chunkDur = 0;
							break;
						}
					} else {
						forceNewChunk = tmp->chunkDur ? 0 : 1;
					}
					//OK, we can write this track
					curWriter = tmp;
					
					//small check for first 2 samples (DTS = 0 :)
					if (tmp->sampleNumber == 2 && !tmp->chunkDur) forceNewChunk = 0;

					tmp->chunkDur += (DTS - tmp->DTSprev);
					tmp->DTSprev = DTS;

					e = stbl_GetSampleInfos(curWriter->mdia->information->sampleTable, curWriter->sampleNumber, &sampOffset, &chunkNumber, &descIndex, &isEdited);
					if (e) return e;
					e = stbl_GetSampleSize(curWriter->mdia->information->sampleTable->SampleSize, curWriter->sampleNumber, &sampSize);
					if (e) return e;
					
					//do we actually write, or do we emulate ?
					if (Emulation) {
						//update our offsets...
						if (Media_IsSelfContained(curWriter->mdia, descIndex) ) {
							e = stbl_SetChunkAndOffset(curWriter->mdia->information->sampleTable, curWriter->sampleNumber, descIndex, curWriter->stsc, &curWriter->stco, offset, forceNewChunk);
							if (e) return e;
							offset += sampSize;
							mdatSize += sampSize;
						} else {
							//we have a DataRef, so use the offset idicated in sampleToChunk 
							//and ChunkOffset tables...
							e = stbl_SetChunkAndOffset(curWriter->mdia->information->sampleTable, curWriter->sampleNumber, descIndex, curWriter->stsc, &curWriter->stco, sampOffset, 0);
							if (e) return e;
						}
					} else {
						//this is no game, we're writing ....
						if (Media_IsSelfContained(curWriter->mdia, descIndex) ) {
							e = WriteSample(mw, sampSize, sampOffset, isEdited, bs);
							if (e) return e;
						}
					}
					//ok, the sample is done
					if (curWriter->sampleNumber == curWriter->mdia->information->sampleTable->SampleSize->sampleCount) {
						curWriter->isDone = 1;
						//one more track done...
						tracksDone ++;
						break;
					} else {
						curWriter->sampleNumber ++;
					}
				}
			}
			//no sample found, we're done with this group
			if (!curWriter) {
				writeGroup = 0;
				continue;
			}
		}
		//if all our track are done, break
		if (tracksDone == ChainGetCount(writers)) break;
		//go to next group
		curGroupID ++;
	}
	movie->mdat->dataSize = mdatSize;
	return M4OK;
}


M4Err WriteInterleaved(MovieWriter *mw, u8 moovFirst, BitStream *bs)
{
	M4Err e;
	u32 i;
	Atom *a;
	u64 firstSize, finalSize, offset, finalOffset;
	Chain *writers = NewChain();
	M4File *movie = mw->movie;

	//first setup the writers
	e = SetupWriters(mw, writers, 1);
	if (e) goto exit;


	if (movie->brand) {
		e = SizeAtom((Atom *)movie->brand);
		if (e) goto exit;
		e = WriteAtom((Atom *)movie->brand, bs);
		if (e) goto exit;
	}
	if (movie->pdin) {
		e = SizeAtom((Atom *)movie->pdin);
		if (e) goto exit;
		e = WriteAtom((Atom *)movie->pdin, bs);
		if (e) goto exit;
	}

	e = DoInterleave(mw, writers, bs, 1, (u32) BS_GetPosition(bs));
	if (e) goto exit;

	firstSize = GetMoovAndMetaSize(movie, writers);
	offset = firstSize + 8 + (movie->mdat->dataSize > 0xFFFFFFFF ? 8 : 0);
	e = ShiftOffset(movie, writers, offset);
	if (e) goto exit;
	//get the size and see if it has changed (eg, we moved to 64 bit offsets)
	finalSize = GetMoovAndMetaSize(movie, writers);
	if (firstSize != finalSize) {
		//we need to remove our offsets
		ResetWriters(writers);
		finalOffset = finalSize + 8 + (movie->mdat->dataSize > 0xFFFFFFFF ? 8 : 0);
		//OK, now we're sure about the final size -> shift the offsets
		//we don't need to re-emulate, as the only thing that changed is the offset
		//so just shift the offset
		e = ShiftOffset(movie, writers, finalOffset - offset);
		if (e) goto exit;
	}
	//now write our stuff
	e = WriteMoovAndMeta(movie, writers, bs);
	if (e) goto exit;

	/*we have 8 extra bytes for large size (not computed in SizeAtom) */
	if (movie->mdat->dataSize > 0xFFFFFFFF) movie->mdat->dataSize += 8;
	e = SizeAtom((Atom *)movie->mdat);
	if (e) goto exit;
	e = WriteAtom((Atom *)movie->mdat, bs);
	if (e) goto exit;

	//we don't need the offset as we are writing...
	ResetWriters(writers);
	e = DoInterleave(mw, writers, bs, 0, 0);
	if (e) goto exit;

	//then the rest
	for (i = 0; i < ChainGetCount(movie->TopAtoms); i++) {
		a = (Atom*)ChainGetEntry(movie->TopAtoms, i);
		switch (a->type) {
		case MovieAtomType:
		case MetaAtomType:
		case FileTypeAtomType:
		case ProgressiveDownloadAtomType:
		case MediaDataAtomType:
			break;
		default:
			e = SizeAtom(a);
			if (e) goto exit;
			e = WriteAtom(a, bs);
			if (e) goto exit;
		}
	}

exit:
	CleanWriters(writers);
	DeleteChain(writers);
	return e;
}


M4Err WriteToFile(M4File *movie, void (*progress)(void *cbk, u32 done, u32 total), void *cbck)
{
	FILE *stream;
	BitStream *bs;
	MovieWriter mw;
	M4Err e = M4OK;
	if (!movie) return M4BadParam;

	if (movie->openMode == M4_OPEN_READ) return M4BadParam;

	e = M4_InsertCopyright(movie);
	if (e) return e;

	memset(&mw, 0, sizeof(mw));
	mw.movie = movie;
	mw.progress = progress;
	mw.cbck = cbck;

	//capture mode: we don't need a new bitstream
	if (movie->openMode == M4_OPEN_WRITE) {
		e = WriteFlat(&mw, 0, movie->editFileMap->bs);
	} else {
		//OK, we need a new bitstream
		stream = f64_open(movie->finalName, "wb");
		if (!stream) return M4IOErr;
		bs = NewBitStreamFromFile(stream, BS_FILE_WRITE_ONLY);
		if (!bs) {
			fclose(stream);
			return M4OutOfMem;
		}

		switch (movie->storageMode) {
		case M4_FULL_INTERLEAVED:
		case M4_INTERLEAVED:
			e = WriteInterleaved(&mw, 0, bs);
			break;
		case M4_STREAMABLE:
			e = WriteFlat(&mw, 1, bs);
			break;
		default:
			e = WriteFlat(&mw, 0, bs);
			break;
		}
		
		DeleteBitStream(bs);
		fclose(stream);
	}
	if (mw.buffer) free(mw.buffer);
	if (progress && (mw.nb_done<mw.total_samples)) {
		progress(cbck, mw.total_samples, mw.total_samples);
	}
	return e;
}



#endif	//M4_READ_ONLY

