/*
 *					GPAC Multimedia Framework
 *
 *			Authors: Cyril Concolato - Jean le Feuvre
 *				Copyright (c) 2005-2005 ENST
 *					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>


static MetaAtom *M4_GetMeta(M4File *file, Bool root_meta, u32 track_num)
{
	TrackAtom *tk;
	if (!file) return NULL;
	if (root_meta) return file->meta;
	if (!track_num) return file->moov ? file->moov->meta : NULL;

	tk = ChainGetEntry(file->moov->trackList, track_num-1);
	return tk ? tk->meta : NULL;
}

u32 M4_GetMetaType(M4File *file, Bool root_meta, u32 track_num)
{
	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta) return 0;
	if (!meta->handler) return 0;
	return meta->handler->handlerType;
}

u32 M4_HasMetaXML(M4File *file, Bool root_meta, u32 track_num)
{
	u32 i, count;
	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta) return 0;

	count = ChainGetCount(meta->other_boxes);
	for (i=0; i<count; i++) {
		Atom *a = ChainGetEntry(meta->other_boxes, i);
		if (a->type == XMLAtomType) return 1;
		if (a->type == BinaryXMLAtomType) return 2;
	}
	return 0;
}

M4Err M4_ExtractMetaXML(M4File *file, Bool root_meta, u32 track_num, char *outName, Bool *is_binary)
{
	u32 i, count;
	FILE *didfile;
	XMLAtom *xml = NULL;
	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta) return M4BadParam;

	/*Find XMLBox*/
	count = ChainGetCount(meta->other_boxes);
	for (i = 0; i <count; i++) {
		Atom *a = ChainGetEntry(meta->other_boxes, i);
		if ((a->type == XMLAtomType) || (a->type == BinaryXMLAtomType) ) {
			xml = (XMLAtom *)a;
			break;
		}
	}
	if (!xml || !xml->xml || !xml->xml_length) return M4BadParam;
	
	didfile = f64_open(outName, "wt");
	if (!didfile) return M4IOErr;
	fwrite(xml->xml, xml->xml_length, 1, didfile);
	fclose(didfile);

	if (is_binary) *is_binary = (xml->type==BinaryXMLAtomType) ? 1 : 0;
	return M4OK;
}



u32 M4_GetMetaItemCount(M4File *file, Bool root_meta, u32 track_num)
{
	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta || !meta->item_infos || !meta->item_locations) return 0;
	return ChainGetCount(meta->item_infos->item_infos);
}

M4Err M4_GetMetaItemInfo(M4File *file, Bool root_meta, u32 track_num, u32 item_num, 
							u32 *itemID, u32 *protection_idx, Bool *is_self_reference,
							const char **item_name, const char **item_mime_type, const char **item_encoding,
							const char **item_url, const char **item_urn)
{
	ItemInfoEntryAtom *iinf;
	u32 i, count;
	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta || !meta->item_infos || !meta->item_locations) return M4BadParam;

	iinf = ChainGetEntry(meta->item_infos->item_infos, item_num-1);
	if (!iinf) return M4BadParam;
	
	if (itemID) (*itemID) = iinf->item_ID;
	if (protection_idx) (*protection_idx) = iinf->item_protection_index;
	if (item_name) (*item_name) = iinf->item_name;
	if (item_mime_type) (*item_mime_type) = iinf->content_type;
	if (item_encoding) (*item_encoding) = iinf->content_encoding;
	if (is_self_reference) *is_self_reference = 0;

	if (item_url) (*item_url) = NULL;
	if (item_urn) (*item_urn) = NULL;

	count = ChainGetCount(meta->item_locations->location_entries);
	for (i=0; i<count; i++) {
		ItemLocationEntry *iloc = ChainGetEntry(meta->item_locations->location_entries, i);
		if (iloc->item_ID==iinf->item_ID) {
			if (iloc->data_reference_index) {
				Atom *a = ChainGetEntry(meta->file_locations->dref->atomList, iloc->data_reference_index-1);
				if (a->type==DataEntryURLAtomType) {
					if (item_url) (*item_url) = ((DataEntryURLAtom*)a)->location;
				} else if (a->type==DataEntryURNAtomType) {
					if (item_url) (*item_url) = ((DataEntryURNAtom*)a)->location;
					if (item_urn) (*item_urn) = ((DataEntryURNAtom*)a)->nameURN;
				}
				break;
			} else if (is_self_reference && !iloc->base_offset) {
				ItemExtentEntry *entry = ChainGetEntry(iloc->extent_entries, 0);
				if (!entry->extent_length && !entry->original_extent_offset)
					*is_self_reference = 1;
			}
		}
	}
	return M4OK;
}

u32 M4_GetMetaItemByID(M4File *file, Bool root_meta, u32 track_num, u32 item_ID)
{
	ItemInfoEntryAtom *iinf;
	u32 i, count;
	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta || !meta->item_infos || !meta->item_locations) return 0;
	count = ChainGetCount(meta->item_infos->item_infos);
	for (i=0; i<count; i++) {
		iinf = ChainGetEntry(meta->item_infos->item_infos, i);
		if (iinf->item_ID==item_ID) return i+1;
	}
	return 0;
}

M4Err M4_ExtractMetaItem(M4File *file, Bool root_meta, u32 track_num, u32 item_num, const char *dump_file_name)
{
	char szPath[1024];
	ItemExtentEntry *extent_entry;
	FILE *resource = NULL;
	u32 i, count;
	ItemInfoEntryAtom *item_entry;
	ItemLocationEntry *location_entry;

	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta || !meta->item_infos || !meta->item_locations) return M4BadParam;

	item_entry = ChainGetEntry(meta->item_infos->item_infos, item_num-1);
	if (!item_entry) return M4BadParam;

	location_entry = NULL;
	count = ChainGetCount(meta->item_locations->location_entries);
	for (i=0; i<count; i++) {
		location_entry = ChainGetEntry(meta->item_locations->location_entries, i);
		if (location_entry->item_ID == item_entry->item_ID) break;
		location_entry = NULL;
	}

	if (!location_entry) return M4BadParam;
	/*FIXME*/
	if (location_entry->data_reference_index) return M4NotSupported;

	/*don't extract self-reference item*/
	count = ChainGetCount(location_entry->extent_entries);
	if (!location_entry->base_offset && (count==1)) {
		extent_entry = ChainGetEntry(location_entry->extent_entries, 0);
		if (!extent_entry->extent_length && !extent_entry->original_extent_offset) return M4BadParam;
	}

	if (dump_file_name) {
		strcpy(szPath, dump_file_name);
	} else {
		if (item_entry->item_name) strcpy(szPath, item_entry->item_name);
		else sprintf(szPath, "item_id%02d", item_entry->item_ID);
	}
	resource = f64_open(szPath, "wb");
		
	for (i=0; i<count; i++) {
		char buf_cache[4096];
		u64 remain;
		ItemExtentEntry *extent_entry = ChainGetEntry(location_entry->extent_entries, i);
		BS_Seek(file->movieFileMap->bs, location_entry->base_offset + extent_entry->extent_offset);

		remain = extent_entry->extent_length;
		while (remain) {
			u32 cache_size = (remain>4096) ? 4096 : (u32) remain;
			BS_ReadData(file->movieFileMap->bs, buf_cache, cache_size);
			fwrite(buf_cache, 1, cache_size, resource);
			remain -= cache_size;
		}
	}
	fclose(resource);
	return M4OK;
}

u32 M4_GetMetaPrimaryItemID(M4File *file, Bool root_meta, u32 track_num)
{
	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta || !meta->primary_resource) return 0;
	return meta->primary_resource->item_ID;
}


#ifndef M4_READ_ONLY


M4Err M4_SetMetaType(M4File *file, Bool root_meta, u32 track_num, u32 metaType)
{
	char szType[5], szName[20];
	MetaAtom *meta;

	M4Err e = CanAccessMovie(file, M4_OPEN_WRITE);
	if (e) return e;

	meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta) {
		if (!metaType) return M4OK;
		meta = (MetaAtom *) meta_New();
		if (root_meta) {
			file->meta = meta;
			ChainAddEntry(file->TopAtoms, meta);
		} else {
			M4_InsertMoov(file);
			if (!track_num) {
				file->moov->meta = meta;
			} else {
				TrackAtom *tk = ChainGetEntry(file->moov->trackList, track_num-1);
				if (!tk) {
					DelAtom((Atom *)meta);
					return M4BadParam;
				}
				tk->meta = meta;
			}
		}
	} else if (!metaType) {
		if (root_meta) {
			ChainDeleteItem(file->TopAtoms, meta);
			DelAtom((Atom *)file->meta);
			file->meta = NULL;
		} else if (file->moov) {
			if (!track_num) {
				DelAtom((Atom *)file->moov->meta);
				file->moov->meta = NULL;
			} else {
				TrackAtom *tk = ChainGetEntry(file->moov->trackList, track_num-1);
				if (!tk) return M4BadParam;
				DelAtom((Atom *)tk->meta);
				tk->meta = NULL;
			}
		}
		return M4OK;
	}

	if (!meta->handler) 
		meta->handler = (HandlerAtom *)hdlr_New();

	if (meta->handler->nameUTF8) free(meta->handler->nameUTF8);
	meta->handler->handlerType = metaType;
	sprintf(szName, "GPAC %s Handler", FCC_TO_STR(metaType, szType));
	meta->handler->nameLength = strlen(szName) + 1;
	meta->handler->nameUTF8 = strdup(szName);
	return M4OK;
}

M4Err M4_RemoveMetaXML(M4File *file, Bool root_meta, u32 track_num)
{
	u32 i;
	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta) return M4BadParam;
	for (i=0; i<ChainGetCount(meta->other_boxes); i++) {
		Atom *a = ChainGetEntry(meta->other_boxes, i);
		switch (a->type) {
		case XMLAtomType:
		case BinaryXMLAtomType:
			ChainDeleteEntry(meta->other_boxes, i);
			DelAtom(a);
			return M4OK;
		}
	}
	return M4OK;
}

M4Err M4_SetMetaXML(M4File *file, Bool root_meta, u32 track_num, char *XMLFileName, Bool IsBinaryXML)
{
	M4Err e;
	FILE *didfile;
	XMLAtom *xml;
	MetaAtom *meta;

	e = CanAccessMovie(file, M4_OPEN_WRITE);
	if (e) return e;

	meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta) return M4BadParam;

	e = M4_RemoveMetaXML(file, root_meta, track_num);
	if (e) return e;

	xml = (XMLAtom *)xml_New();
	if (!xml) return M4OutOfMem;
	ChainAddEntry(meta->other_boxes, xml);
	if (IsBinaryXML) xml->type = BinaryXMLAtomType;


	/*assume 32bit max size = 4Go should be sufficient for a DID!!*/
	didfile = fopen(XMLFileName, "rt");
	if (!didfile) return M4URLNotFound;
	fseek(didfile, 0, SEEK_END);
	xml->xml_length = ftell(didfile);
	fseek(didfile, 0, SEEK_SET);
	xml->xml = malloc(sizeof(unsigned char)*xml->xml_length);
	xml->xml_length = fread(xml->xml, 1, sizeof(unsigned char)*xml->xml_length, didfile);
	if (ferror(didfile)) {
		free(xml->xml);
		xml->xml = NULL;
		return M4BadParam;
	}
	fclose(didfile);
	return M4OK;
}


M4Err M4_AddMetaItem(M4File *file, Bool root_meta, u32 track_num, Bool self_reference, char *resource_path, const char *item_name, const char *mime_type, const char *content_encoding, const char *URL, const char *URN)
{
	M4Err e;
	ItemLocationEntry *location_entry;
	ItemInfoEntryAtom *infe;
	MetaAtom *meta;
	u32 lastItemID = 0;

	if (!self_reference && !item_name && !resource_path) return M4BadParam;
	e = CanAccessMovie(file, M4_OPEN_WRITE);
	if (e) return e;
	meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta) return M4BadParam;

	e = FlushCaptureMode(file);
	if (e) return e;

	/*check file exists */
	if (!URN && !URL && !self_reference) {
		FILE *src = fopen(resource_path, "rb");
		if (!src) return M4URLNotFound;
		fclose(src);
	}

	if (meta->item_infos) {
		u32 i;
		u32 item_count = ChainGetCount(meta->item_infos->item_infos);
		for (i = 0; i < item_count; i++) {
			ItemInfoEntryAtom *e= ChainGetEntry(meta->item_infos->item_infos, i);
			if (e->item_ID > lastItemID) lastItemID = e->item_ID;
		}
	}

	infe = (ItemInfoEntryAtom *)infe_New();
	infe->item_ID = ++lastItemID;

	/*get relative name*/
	if (item_name) {
		infe->item_name = strdup(item_name);
	} else if (resource_path) {
		if (strrchr(resource_path, M4_PATH_SEPARATOR)) {
			infe->item_name = strdup(strrchr(resource_path, M4_PATH_SEPARATOR) + 1);
		} else {
			infe->item_name = strdup(resource_path);
		}
	}

	if (mime_type) {
		infe->content_type = strdup(mime_type);
	} else {
		infe->content_type = strdup("application/octet-stream");
	}
	if (content_encoding) infe->content_encoding = strdup(content_encoding);

	/*Creation of the ItemLocation */
	location_entry = malloc(sizeof(ItemLocationEntry));
	if (!location_entry) {
		DelAtom((Atom *)infe);
		return M4OutOfMem;
	}
	memset(location_entry, 0, sizeof(ItemLocationEntry));
	location_entry->extent_entries = NewChain();
		
	/*Creates an mdat if it does not exist*/
	if (!file->mdat) {
		file->mdat = (MediaDataAtom *)mdat_New();
		ChainAddEntry(file->TopAtoms, file->mdat);
	}

	/*Creation an ItemLocation Box if it does not exist*/
	if (!meta->item_locations) meta->item_locations = (ItemLocationAtom *)iloc_New();
	ChainAddEntry(meta->item_locations->location_entries, location_entry);
	location_entry->item_ID = lastItemID;

	if (!meta->item_infos) meta->item_infos = (ItemInfoAtom *) iinf_New();
	e = ChainAddEntry(meta->item_infos->item_infos, infe);
	if (e) return e;

	/*0: the current file*/
	location_entry->data_reference_index = 0;
	if (self_reference) {
		ItemExtentEntry *entry;
		SAFEALLOC(entry, sizeof(ItemExtentEntry));
		ChainAddEntry(location_entry->extent_entries, entry);
		if (!infe->item_name) infe->item_name = strdup("");
		return M4OK;
	}

	/*file not copied, just referenced*/
	if (URL || URN) {
		u32 dataRefIndex;
		if (!meta->item_locations) meta->file_locations = (DataInformationAtom *) CreateAtom(DataInformationAtomType);
		if (!meta->file_locations->dref) meta->file_locations->dref = (DataReferenceAtom *) CreateAtom(DataReferenceAtomType);
		e = Media_FindDataRef(meta->file_locations->dref, (char *) URL, (char *) URN, &dataRefIndex);
		if (e) return e;
		if (!dataRefIndex) {
			e = Media_CreateDataRef(meta->file_locations->dref, (char *) URL, (char *) URN, &dataRefIndex);
			if (e) return e;
		}
		location_entry->data_reference_index = dataRefIndex;
	}

	/*capture mode, write to disk*/
	if ((file->openMode == M4_OPEN_WRITE) && !location_entry->data_reference_index) {
		FILE *src;
		ItemExtentEntry *entry;
		SAFEALLOC(entry, sizeof(ItemExtentEntry));

		location_entry->base_offset = BS_GetPosition(file->editFileMap->bs);

		/*update base offset size*/
		if (location_entry->base_offset>0xFFFFFFFF) meta->item_locations->base_offset_size = 8;
		else if (location_entry->base_offset && !meta->item_locations->base_offset_size) meta->item_locations->base_offset_size = 4;

		entry->extent_length = 0;
		entry->extent_offset = 0;
		ChainAddEntry(location_entry->extent_entries, entry);

		src = f64_open(resource_path, "rb");
		if (src) {
			char cache_data[4096];
			u64 remain;
			f64_seek(src, 0, SEEK_END);
			entry->extent_length = f64_tell(src);
			f64_seek(src, 0, SEEK_SET);

			remain = entry->extent_length;
			while (remain) {
				u32 size_cache = (remain>4096) ? 4096 : (u32) remain;
				fread(cache_data, 1, size_cache, src);
				BS_WriteData(file->editFileMap->bs, cache_data, size_cache);
				remain -= size_cache;
			}
			fclose(src);

			/*update length size*/
			if (entry->extent_length>0xFFFFFFFF) meta->item_locations->length_size = 8;
			else if (entry->extent_length && !meta->item_locations->length_size) meta->item_locations->length_size = 4;
		}
	}
	/*store full path for info*/
	else if (!location_entry->data_reference_index) {
		infe->full_path = strdup(resource_path);
	}
	return M4OK;
}


M4Err M4_RemoveMetaItem(M4File *file, Bool root_meta, u32 track_num, u32 item_num)
{
	ItemInfoEntryAtom *iinf;
	u32 i, count;
	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta || !meta->item_infos || !meta->item_locations) return M4BadParam;

	iinf = ChainGetEntry(meta->item_infos->item_infos, item_num-1);
	if (!iinf) return M4BadParam;
	ChainDeleteEntry(meta->item_infos->item_infos, item_num-1);

	count = ChainGetCount(meta->item_locations->location_entries);
	for (i=0; i<count; i++) {
		ItemLocationEntry *iloc = ChainGetEntry(meta->item_locations->location_entries, i);
		if (iloc->item_ID==iinf->item_ID) {
			/*FIXME: remove data ref...*/
			if (iloc->data_reference_index) { }

			ChainDeleteEntry(meta->item_locations->location_entries, i);
			DelAtom((Atom *)iloc);
			break;
		}
	}
	DelAtom((Atom *)iinf);
	return M4OK;
}

M4Err M4_SetMetaPrimaryItem(M4File *file, Bool root_meta, u32 track_num, u32 item_num)
{
	ItemInfoEntryAtom *infe;
	MetaAtom *meta = M4_GetMeta(file, root_meta, track_num);
	if (!meta || !meta->item_infos || !meta->item_locations) return M4BadParam;
	/*either one or the other*/
	if (M4_HasMetaXML(file, root_meta, track_num)) return M4BadParam;

	if (!item_num) {
		if (meta->primary_resource) DelAtom((Atom*)meta->primary_resource);
		meta->primary_resource = NULL;
		return M4OK;
	}
	if (!meta->primary_resource) meta->primary_resource = (PrimaryItemAtom*) CreateAtom(PrimaryItemAtomType);

	infe = ChainGetEntry(meta->item_infos->item_infos, item_num-1);
	meta->primary_resource->item_ID = infe->item_ID;
	return M4OK;
}


#endif	//M4_READ_ONLY


