/*
 * Copyright (C) 2017-2023 Adrien Gesta-Fline
 *
 * This file is part of libAAF.
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/**
 * @file LibAAF/AAFIface/AAFIParser.c
 * @brief AAF processing
 * @author Adrien Gesta-Fline
 * @version 0.1
 * @date 27 june 2018
 *
 * @ingroup AAFIface
 * @addtogroup AAFIface
 *
 *
 *
 *
 * @{
 */

#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include <math.h>

#include "aaf/AAFDump.h"
#include "aaf/AAFIAudioFiles.h"
#include "aaf/AAFIParser.h"
#include "aaf/AAFIface.h"
#include "aaf/AAFToText.h"
#include "aaf/debug.h"

#include "aaf/ProTools.h"
#include "aaf/Resolve.h"

#include "aaf/AAFDefs/AAFClassDefUIDs.h"
#include "aaf/AAFDefs/AAFPropertyIDs.h"
// #include "aaf/AAFDefs/AAFCompressionDefs.h"
#include "aaf/AAFDefs/AAFDataDefs.h"
#include "aaf/AAFDefs/AAFExtEnum.h"
#include "aaf/AAFDefs/AAFInterpolatorDefs.h"
#include "aaf/AAFDefs/AAFOperationDefs.h"
#include "aaf/AAFDefs/AAFParameterDefs.h"
#include "aaf/AAFDefs/AAFTypeDefUIDs.h"
// #include "aaf/AAFDefs/AAFFileKinds.h"
#include "aaf/AAFDefs/AAFOPDefs.h"
// #include "aaf/AAFDefs/AAFContainerDefs.h"

#include "aaf/utils.h"

#define debug(...) \
	_dbg (aafi->dbg, aafi, DEBUG_SRC_ID_AAF_IFACE, VERB_DEBUG, __VA_ARGS__)

#define warning(...) \
	_dbg (aafi->dbg, aafi, DEBUG_SRC_ID_AAF_IFACE, VERB_WARNING, __VA_ARGS__)

#define error(...) \
	_dbg (aafi->dbg, aafi, DEBUG_SRC_ID_AAF_IFACE, VERB_ERROR, __VA_ARGS__)

// #define trace( ... )
// 	_dbg( aafi->dbg, aafi, LIB_AAF_IFACE_TRACE, 0, __VA_ARGS__ )

static aafRational_t AAFI_DEFAULT_TC_EDIT_RATE = { 25, 1 };

#define RESET_CONTEXT(ctx)                              \
	/*ctx.MobSlot = NULL;*/                         \
	ctx.current_track = NULL;                       \
	/*ctx.current_pos = 0;*/                        \
	ctx.current_transition                  = NULL; \
	ctx.current_clip_gain                   = NULL; \
	ctx.current_clip_automation             = NULL; \
	ctx.current_essence                     = NULL; \
	ctx.current_clip                        = NULL; \
	ctx.current_clip_is_muted               = 0;    \
	ctx.current_clip_is_combined            = 0;    \
	ctx.current_combined_clip_total_channel = 0;    \
	ctx.current_combined_clip_channel_num   = 0;

// ctx.current_track_is_multichannel = 0;
// ctx.current_multichannel_track_channel = 0;
// ctx.current_multichannel_track_clip_length = 0;

// static void aafi_trace_obj( AAF_Iface *aafi, aafObject *Obj, char *color );

static wchar_t*
build_unique_audiofilename (AAF_Iface* aafi, aafiAudioEssence* audioEssence);
static wchar_t*
build_unique_videofilename (AAF_Iface* aafi, aafiVideoEssence* videoEssence);

/* TODO move to AAFCore.c */
static aafObject*
get_Object_Ancestor (AAF_Iface* aafi, aafObject* Obj, const aafUID_t* ClassID);

static aafUID_t*
get_Component_DataDefinition (AAF_Iface* aafi, aafObject* Component);
// static aafUID_t  * get_FileDescriptor_ContainerFormat( AAF_Iface *aafi, aafObject *FileDescriptor );
static aafUID_t*
get_OperationGroup_OperationIdentification (AAF_Iface* aafi, aafObject* OperationGroup);
static aafUID_t*
get_Parameter_InterpolationIdentification (AAF_Iface* aafi, aafObject* Parameter);

static aafObject*
get_EssenceData_By_MobID (AAF_Iface* aafi, aafMobID_t* MobID);

// static aafiAudioEssence * getAudioEssenceBySourceMobID( AAF_Iface *aafi, aafMobID_t *sourceMobID );
// static aafiVideoEssence * getVideoEssenceBySourceMobID( AAF_Iface *aafi, aafMobID_t *sourceMobID );

static int
parse_DigitalImageDescriptor (AAF_Iface* aafi, aafObject* DIDescriptor, td* __ptd);
static int
parse_CDCIDescriptor (AAF_Iface* aafi, aafObject* CDCIDescriptor, td* __ptd);

static int
parse_EssenceDescriptor (AAF_Iface* aafi, aafObject* EssenceDesc, td* __ptd);
static int
parse_PCMDescriptor (AAF_Iface* aafi, aafObject* PCMDescriptor, td* __ptd);
static int
parse_WAVEDescriptor (AAF_Iface* aafi, aafObject* WAVEDescriptor, td* __ptd);
static int
parse_AIFCDescriptor (AAF_Iface* aafi, aafObject* AIFCDescriptor, td* __ptd);

static int
parse_Locator (AAF_Iface* aafi, aafObject* Locator, td* __ptd);
static int
parse_NetworkLocator (AAF_Iface* aafi, aafObject* NetworkLocator, td* __ptd);

static int
parse_EssenceData (AAF_Iface* aafi, aafObject* EssenceData, td* __ptd);

static int
parse_Component (AAF_Iface* aafi, aafObject* Component, td* __ptd);
static int
parse_Transition (AAF_Iface* aafi, aafObject* Transition, td* __ptd);
static int
parse_NestedScope (AAF_Iface* aafi, aafObject* NestedScope, td* __ptd);
static int
parse_Filler (AAF_Iface* aafi, aafObject* Filler, td* __ptd);
static int
parse_Sequence (AAF_Iface* aafi, aafObject* Sequence, td* __ptd);
static int
parse_Timecode (AAF_Iface* aafi, aafObject* Timecode, td* __ptd);
static int
parse_OperationGroup (AAF_Iface* aafi, aafObject* OpGroup, td* __ptd);
static int
parse_SourceClip (AAF_Iface* aafi, aafObject* SourceClip, td* __ptd);
static int
parse_Selector (AAF_Iface* aafi, aafObject* Selector, td* __ptd);

static int
parse_Parameter (AAF_Iface* aafi, aafObject* Parameter, td* __ptd);
static int
parse_ConstantValue (AAF_Iface* aafi, aafObject* ConstantValue, td* __ptd);
static int
parse_VaryingValue (AAF_Iface* aafi, aafObject* VaryingValue, td* __ptd);
static int
retrieve_ControlPoints (AAF_Iface* aafi, aafObject* Points, aafRational_t* times[], aafRational_t* values[]);

static int
parse_Mob (AAF_Iface* aafi, aafObject* Mob);
static int
parse_CompositionMob (AAF_Iface* aafi, aafObject* CompoMob, td* __ptd);
static int
parse_SourceMob (AAF_Iface* aafi, aafObject* SourceMob, td* __ptd);

static int
parse_MobSlot (AAF_Iface* aafi, aafObject* MobSlot, td* __ptd);

static void
xplore_StrongObjectReferenceVector (AAF_Iface* aafi, aafObject* ObjCollection, td* __ptd);

static void
xplore_StrongObjectReferenceVector (AAF_Iface* aafi, aafObject* ObjCollection, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	// aaf_dump_ObjectProperties( aafi->aafd, ComponentAttributeList );

	struct dbg* dbg = aafi->dbg;
	aafObject*  Obj = NULL;

	aaf_foreach_ObjectInSet (&Obj, ObjCollection, NULL)
	{
		// aaf_dump_ObjectProperties( aafi->aafd, ObjCollection );
		int offset = 0;
		/* TODO implement retrieve_TaggedValue() */

		if (aaf_get_property (Obj, PID_TaggedValue_Name) &&
		    aaf_get_property (Obj, PID_TaggedValue_Value)) {
			wchar_t*       name     = aaf_get_propertyValue (Obj, PID_TaggedValue_Name, &AAFTypeID_String);
			aafIndirect_t* indirect = aaf_get_propertyValue (Obj, PID_TaggedValue_Value, &AAFTypeID_Indirect);

			if (aafUIDCmp (&indirect->TypeDef, &AAFTypeID_Int32)) {
				int32_t* indirectValue = aaf_get_indirectValue (aafi->aafd, indirect, &AAFTypeID_Int32);
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "Tagged     |     Name: %ls%*s      Value (%ls)  : %i\n", name, 56 - (int)wcslen (name), " ", aaft_TypeIDToText (&indirect->TypeDef), *indirectValue);
			} else if (aafUIDCmp (&indirect->TypeDef, &AAFTypeID_String)) {
				wchar_t* indirectValue = aaf_get_indirectValue (aafi->aafd, indirect, &AAFTypeID_String);
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "Tagged     |     Name: %ls%*s      Value (%ls) : %ls\n", name, 56 - (int)wcslen (name), " ", aaft_TypeIDToText (&indirect->TypeDef), indirectValue);
				free (indirectValue);
			} else {
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "Tagged     |     Name: %ls%*s      Value (%s%ls%s) : %sUNKNOWN_TYPE%s\n", name, 56 - (int)wcslen (name), " ", ANSI_COLOR_RED, aaft_TypeIDToText (&indirect->TypeDef), ANSI_COLOR_RESET, ANSI_COLOR_RED, ANSI_COLOR_RESET);
			}

			dbg->debug_callback (dbg, (void*)aafi, DEBUG_SRC_ID_DUMP, 0, "", "", 0, dbg->_dbg_msg, dbg->user);

			free (name);
		} else {
			dbg->debug_callback (dbg, (void*)aafi, DEBUG_SRC_ID_DUMP, 0, "", "", 0, dbg->_dbg_msg, dbg->user);

			offset = 0;
			aaf_dump_ObjectProperties (aafi->aafd, Obj);
		}
	}
}

void
aafi_dump_obj (AAF_Iface* aafi, aafObject* Obj, struct trace_dump* __td, int state, int line, const char* fmt, ...)
{
	if (aafi->ctx.options.trace == 0)
		return;

	/* Print caller line number */
	struct dbg* dbg    = aafi->dbg;
	int         offset = 0;

	if (Obj) {
		switch (state) {
			case TD_ERROR:
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_RED);
				break;
			case TD_WARNING:
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_YELLOW);
				break;
			case TD_NOT_SUPPORTED:
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_ORANGE);
				break;
			default:
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_DARKGREY);
				break;
		}
		offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%05i", line);
	} else {
		offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "     ");
	}

	offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s%ls%s", ANSI_COLOR_DARKGREY, L"\u2502", ANSI_COLOR_RESET); // │

	/* Print padding and vertical lines */

	if (__td->lv > 0) {
		for (int i = 0; i < __td->lv; i++) {
			/* current level iteration has more than one entry remaining in loop */

			if (__td->ll[i] > 1) {
				/* next level iteration is current trace */

				if (i + 1 == __td->lv) {
					if (Obj) {
						offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%ls", L"\u251c\u2500\u2500\u25fb "); // ├──◻
					} else {
						offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%ls", L"\u2502    "); // │
					}
				} else {
					offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%ls", L"\u2502    "); // │
				}
			} else if (i + 1 == __td->lv && Obj) {
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%ls", L"\u2514\u2500\u2500\u25fb "); // └──◻
			} else {
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "     ");
			}
		}
	}

	if (Obj) {
		switch (state) {
			case TD_ERROR:
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_RED);
				break;
			case TD_WARNING:
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_YELLOW);
				break;
			case TD_NOT_SUPPORTED:
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_ORANGE);
				break;
			case TD_INFO:
			case TD_OK:
				if (__td->sub)
					offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_DARKGREY);
				else
					offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_CYAN);

				break;
		}

		offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%ls ", aaft_ClassIDToText (aafi->aafd, Obj->Class->ID));

		offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_RESET);

		if (aafUIDCmp (Obj->Class->ID, &AAFClassID_TimelineMobSlot) &&
		    aafUIDCmp (Obj->Parent->Class->ID, &AAFClassID_CompositionMob)) {
			aafObject* Segment        = aaf_get_propertyValue (Obj, PID_MobSlot_Segment, &AAFTypeID_SegmentStrongReference);
			aafUID_t*  DataDefinition = get_Component_DataDefinition (aafi, Segment);
			wchar_t*   name           = aaf_get_propertyValue (Obj, PID_MobSlot_SlotName, &AAFTypeID_String);
			uint32_t*  slotID         = aaf_get_propertyValue (Obj, PID_MobSlot_SlotID, &AAFTypeID_UInt32);
			uint32_t*  trackNo        = aaf_get_propertyValue (Obj, PID_MobSlot_PhysicalTrackNumber, &AAFTypeID_UInt32);

			offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "[slot:%s%i%s track:%s%i%s] (DataDef : %s%ls%s) %s%ls ",
			                                      ANSI_COLOR_BOLD,
			                                      (slotID) ? (int)(*slotID) : -1,
			                                      ANSI_COLOR_RESET,
			                                      ANSI_COLOR_BOLD,
			                                      (trackNo) ? (int)(*trackNo) : -1,
			                                      ANSI_COLOR_RESET,
			                                      ANSI_COLOR_DARKGREY,
			                                      aaft_DataDefToText (aafi->aafd, DataDefinition),
			                                      ANSI_COLOR_RESET,
			                                      (name[0] != 0x00) ? ": " : "", (name) ? name : L"");

			free (name);
		} else if (aafUIDCmp (Obj->Class->ID, &AAFClassID_CompositionMob) ||
		           aafUIDCmp (Obj->Class->ID, &AAFClassID_MasterMob) ||
		           aafUIDCmp (Obj->Class->ID, &AAFClassID_SourceMob)) {
			aafUID_t* usageCode = aaf_get_propertyValue (Obj, PID_Mob_UsageCode, &AAFTypeID_UsageType);
			wchar_t*  name      = aaf_get_propertyValue (Obj, PID_Mob_Name, &AAFTypeID_String);

			offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "(UsageCode: %s%ls%s) %s%ls",
			                                      ANSI_COLOR_DARKGREY,
			                                      aaft_UsageCodeToText (usageCode),
			                                      ANSI_COLOR_RESET,
			                                      (name && name[0] != 0x00) ? ": " : "", (name) ? name : L"");

			free (name);
		} else if (aafUIDCmp (Obj->Class->ID, &AAFClassID_OperationGroup)) {
			aafUID_t* OperationIdentification = get_OperationGroup_OperationIdentification (aafi, Obj);

			offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "(OpIdent: %s%ls%s) ",
			                                      ANSI_COLOR_DARKGREY,
			                                      aaft_OperationDefToText (aafi->aafd, OperationIdentification),
			                                      ANSI_COLOR_RESET);
		}
		// else if ( aafUIDCmp( Obj->Class->ID, &AAFClassID_TapeDescriptor ) ||
		//           aafUIDCmp( Obj->Class->ID, &AAFClassID_FilmDescriptor ) ||
		//           aafUIDCmp( Obj->Class->ID, &AAFClassID_CDCIDescriptor ) ||
		//           aafUIDCmp( Obj->Class->ID, &AAFClassID_RGBADescriptor ) ||
		//           aafUIDCmp( Obj->Class->ID, &AAFClassID_TIFFDescriptor ) ||
		//           aafUIDCmp( Obj->Class->ID, &AAFClassID_SoundDescriptor ) ||
		//           aafUIDCmp( Obj->Class->ID, &AAFClassID_PCMDescriptor ) ||
		//           aafUIDCmp( Obj->Class->ID, &AAFClassID_AES3PCMDescriptor ) ||
		//           aafUIDCmp( Obj->Class->ID, &AAFClassID_WAVEDescriptor ) ||
		//           aafUIDCmp( Obj->Class->ID, &AAFClassID_AIFCDescriptor ) )
		// {
		// 	aafUID_t *ContainerFormat = get_FileDescriptor_ContainerFormat( aafi, Obj );
		// 	offset += laaf_util_snprintf_realloc( &dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "(ContainerIdent : \x1b[38;5;242m%ls\x1b[0m)", aaft_ContainerToText(ContainerFormat) );
		// }

		if (state == TD_ERROR) {
			offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, ": %s", ANSI_COLOR_RED);
		} else if (state == TD_INFO) {
			offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, ": %s", ANSI_COLOR_CYAN);
		}

		va_list args;
		va_start (args, fmt);

		offset += laaf_util_vsnprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, fmt, &args);

		va_end (args);
		// va_list args;
		// va_list args2;
		// va_copy( args2, args );
		//
		// va_start( args2, fmt );
		// // vprintf(fmt, args);
		// // offset += laaf_util_vsnprintf_realloc( &dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, fmt, args );
		// int needed = vsnprintf( NULL, 0, fmt, args2 ) + 1;
		// if ( needed >= dbg->_dbg_msg_size + offset ) {
		// 	char *p = realloc( dbg->_dbg_msg, offset+needed );
		// 	if (p) {
		// 	dbg->_dbg_msg = p;
		// 	} else {
		// 		/* TODO: realloc() faillure */
		// 		// free(*str);
		// 		// *str  = NULL;
		// 		// *size = 0;
		// 		// return -1;
		// 	}
		// }
		// va_end( args2 );
		//
		// va_start( args, fmt );
		// // vprintf( fmt, args );
		// offset += vsnprintf( dbg->_dbg_msg+offset, dbg->_dbg_msg_size-offset, fmt, args );
		// va_end( args );

		if (state == TD_ERROR || state == TD_INFO) {
			offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, ".");
		}

		if (state == TD_NOT_SUPPORTED || (aafi->ctx.options.trace_class && wcscmp (aaft_ClassIDToText (aafi->aafd, Obj->Class->ID), aafi->ctx.options.trace_class) == 0)) {
			offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "\n%s", (state == TD_NOT_SUPPORTED) ? ANSI_COLOR_ORANGE : "");

			// offset += laaf_util_snprintf_realloc( &dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "CFB Object Dump : %ls\n", aaf_get_ObjectPath( Obj ) );
			// offset += laaf_util_snprintf_realloc( &dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "=================\n" );
			// cfb_dump_node( aafi->aafd->cfbd, Obj->Node, 1 );

			offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "Properties Dump (%ls)\n", aaf_get_ObjectPath (Obj));
			offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "===============\n\n");
			// aaf_dump_nodeStreamProperties( aafi->aafd, Obj->Node );

			// dbg->debug_callback( dbg, (void*)aafi, DEBUG_SRC_ID_TRACE, 0, "", "", 0, dbg->_dbg_msg, dbg->user );
			//
			// offset = 0;
			// aaf_dump_ObjectProperties( aafi->aafd, Obj );
		} else {
			aafProperty* Prop            = NULL;
			int          hasUnknownProps = 0;

			for (Prop = Obj->Properties; Prop != NULL; Prop = Prop->next) {
				if (Prop->def->meta) {
					// offset += laaf_util_snprintf_realloc( &dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "\n");

					if (aafi->ctx.options.trace_meta) {
						// aaf_dump_ObjectProperties( aafi->aafd, Obj );

						// if ( Prop->pid == 0xffca ) {
						if (Prop->sf == SF_STRONG_OBJECT_REFERENCE_VECTOR) {
							offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "\n");
							offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, " >>> (0x%04x) %ls (%ls)\n", Prop->pid, aaft_PIDToText (aafi->aafd, Prop->pid), aaft_StoredFormToText (Prop->sf) /*AUIDToText( &Prop->def->type ),*/ /*aaft_TypeIDToText( &(Prop->def->type) )*/);
							void* propValue = aaf_get_propertyValue (Obj, Prop->pid, &AAFUID_NULL);
							xplore_StrongObjectReferenceVector (aafi, propValue, __td);

							// DUMP_OBJ_NO_SUPPORT( aafi, propValue, __td );
						} else {
							offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "\n");
							aaf_dump_ObjectProperty (aafi->aafd, Prop);
						}
					} else {
						offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s%s %ls[0x%04x]", ANSI_COLOR_RESET, (!hasUnknownProps) ? "  (MetaProps:" : "", aaft_PIDToText (aafi->aafd, Prop->pid), Prop->pid);
						// laaf_util_dump_hex( Prop->val, Prop->len );
						hasUnknownProps++;
					}
				}
			}
			if (aafi->ctx.options.trace_meta == 0 && hasUnknownProps) {
				offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, ")");
			}
		}

		offset += laaf_util_snprintf_realloc (&dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "%s", ANSI_COLOR_RESET);
	}

	// offset += laaf_util_snprintf_realloc( &dbg->_dbg_msg, &dbg->_dbg_msg_size, offset, "\n" );

	dbg->debug_callback (dbg, (void*)aafi, DEBUG_SRC_ID_TRACE, 0, "", "", 0, dbg->_dbg_msg, dbg->user);

	/* if end of branch, print one line padding */
	if (Obj && (__td->eob || state == TD_ERROR))
		aafi_dump_obj (aafi, NULL, __td, 0, -1, "");
}

void
aafi_dump_obj_no_support (AAF_Iface* aafi, aafObject* Obj, struct trace_dump* __td, int line)
{
	// aafUID_t *DataDefinition = NULL;

	if (aafUIDCmp (Obj->Class->ID, &AAFClassID_TimelineMobSlot) &&
	    aafUIDCmp (Obj->Parent->Class->ID, &AAFClassID_CompositionMob)) {
		/* this part is handled by aafi_dump_obj() already. */
		aafi_dump_obj (aafi, Obj, __td, TD_NOT_SUPPORTED, line, "");
		return;
		// 	aafObject *Segment = aaf_get_propertyValue( Obj, PID_MobSlot_Segment, &AAFTypeID_SegmentStrongReference );
		//
		// 	if ( Segment != NULL ) /* req */ {
		// 		DataDefinition = get_Component_DataDefinition( aafi, Segment );
		// 	}
		// }
		// else {
		// 	DataDefinition = get_Component_DataDefinition( aafi, Obj );
	}

	// DataDefinition = get_Component_DataDefinition( aafi, Obj );

	aafi_dump_obj (aafi, Obj, __td, TD_NOT_SUPPORTED, line, "");

	// aafi_dump_obj( aafi, Obj, __td, WARNING, line, "%s%ls%s",
	// 	// aaft_ClassIDToText( aafi->aafd, Obj->Class->ID ),
	// 	((DataDefinition) ? "(Segment DataDefinition: \x1b[38;5;242m" : ""),
	// 	((DataDefinition) ? aaft_DataDefToText( aafi->aafd, DataDefinition ) : L""),
	// 	((DataDefinition) ? ") \x1b[0m" : "") );
}

/*
#define DUMP_OBJ( aafi, Obj, __td ) \
	aafi_dump_obj( aafi, Obj, __td, OK, __LINE__, "" );

#define DUMP_OBJ_ERROR( aafi, Obj, __td, ... ) \
	(__td)->eob = 1; \
	aafi_dump_obj( aafi, Obj, __td, ERROR, __LINE__, __VA_ARGS__ );

#define DUMP_OBJ_WARNING( aafi, Obj, __td, ... ) \
	aafi_dump_obj( aafi, Obj, __td, WARNING, __LINE__, __VA_ARGS__ );

#define DUMP_OBJ_INFO( aafi, Obj, __td, ... ) \
	aafi_dump_obj( aafi, Obj, __td, OK, __LINE__, __VA_ARGS__ );

#define DUMP_OBJ_NO_SUPPORT( aafi, Obj, __td ) \
	(__td)->eob = 1; \
	aafi_dump_obj_no_support( aafi, Obj, __td, __LINE__ ); \
	// aaf_dump_ObjectProperties( aafi->aafd, Obj );
*/

static wchar_t*
build_unique_audiofilename (AAF_Iface* aafi, aafiAudioEssence* audioEssence)
{
	wchar_t* unique        = NULL;
	size_t   unique_size   = 0;
	size_t   file_name_len = 0;

	if (audioEssence->file_name) {
		file_name_len = wcslen (audioEssence->file_name);
		unique_size   = file_name_len + 1 + 4; // +4 = "_001"
		unique_size   = (unique_size < AAFUID_PRINTED_LEN + 1) ? AAFUID_PRINTED_LEN + 1 : unique_size;

		// debug("%lu, %lu", file_name_len, unique_size);

		unique = malloc (sizeof (wchar_t) * unique_size);

		if (unique == NULL) {
			error ("Could not allocate memory : %s", strerror (errno));
			return NULL;
		}

		if (swprintf (unique, unique_size, L"%" WPRIws, audioEssence->file_name) < 0) {
			error ("Could not prepare unique filename");
			return NULL;
		}
	} else {
		file_name_len = strlen ("unknown");
		unique_size   = file_name_len + 1 + 4; // +4 = "_001"
		unique_size   = (unique_size < AAFUID_PRINTED_LEN + 1) ? AAFUID_PRINTED_LEN + 1 : unique_size;

		unique = malloc (sizeof (wchar_t) * unique_size);

		if (unique == NULL) {
			error ("Could not allocate memory : %s", strerror (errno));
			return NULL;
		}

		if (swprintf (unique, unique_size, L"unknown") < 0) {
			error ("Could not prepare unique filename");
			return NULL;
		}
	}

	// debug( "%ls", unique );

	aafiAudioEssence* ae = NULL;

	if (aafi->ctx.options.forbid_nonlatin_filenames && laaf_util_wstr_contains_nonlatin (unique)) {
		aafUID_t* uuid = &(audioEssence->sourceMobID->material);

		int rc = swprintf (unique, unique_size, L"%08x-%04x-%04x-%02x%02x%02x%02x%02x%02x%02x%02x",
		                   uuid->Data1,
		                   uuid->Data2,
		                   uuid->Data3,
		                   uuid->Data4[0],
		                   uuid->Data4[1],
		                   uuid->Data4[2],
		                   uuid->Data4[3],
		                   uuid->Data4[4],
		                   uuid->Data4[5],
		                   uuid->Data4[6],
		                   uuid->Data4[7]);

		if (rc < 0) {
			error ("Failed to set unique filename with SourceMobID UID");
			free (unique);
			return NULL;
		}

		audioEssence->unique_file_name = unique;

		return unique;
	}

	int index = 0;

	foreachEssence (ae, aafi->Audio->Essences)
	{
		if (ae->unique_file_name != NULL && wcscmp (ae->unique_file_name, unique) == 0) {
			if (swprintf (unique + file_name_len, (unique_size - file_name_len), L"_%i", ++index) < 0) {
				error ("Failed to increment unique filename");
				free (unique);
				return NULL;
			}

			ae = aafi->Audio->Essences; // check again
			                            // debug( "%ls", unique );
		}
	}

	audioEssence->unique_file_name = unique;

	// debug( "%ls", audioEssence->wunique_file_name );

	return unique;
}

static wchar_t*
build_unique_videofilename (AAF_Iface* aafi, aafiVideoEssence* videoEssence)
{
	/* TODO 1024 should be a macro ! */

	wchar_t* unique = calloc (sizeof (wchar_t), 1024);

	size_t file_name_len = wcslen (videoEssence->file_name);

	// debug( "%i", file_name_len );

	memcpy (unique, videoEssence->file_name, (file_name_len + 1) * sizeof (wchar_t));

	// debug( "%ls", unique );

	aafiVideoEssence* ve = NULL;

	if (1) {
		size_t i = 0;

		for (; i < file_name_len; i++) {
			/* if char is out of the Basic Latin range */

			if (unique[i] > 0xff) {
				// debug( "MobID : %ls", aaft_MobIDToText( videoEssence->sourceMobID ) );
				aafUID_t* uuid = &(videoEssence->sourceMobID->material);
				swprintf (unique, 1024, L"%08x-%04x-%04x-%02x%02x%02x%02x%02x%02x%02x%02x",
				          uuid->Data1,
				          uuid->Data2,
				          uuid->Data3,
				          uuid->Data4[0],
				          uuid->Data4[1],
				          uuid->Data4[2],
				          uuid->Data4[3],
				          uuid->Data4[4],
				          uuid->Data4[5],
				          uuid->Data4[6],
				          uuid->Data4[7]);

				videoEssence->unique_file_name = unique;

				return unique;
			}
		}
	}

	int id = 0;

	foreachEssence (ve, aafi->Video->Essences)
	{
		if (ve->unique_file_name != NULL && wcscmp (ve->unique_file_name, unique) == 0) {
			swprintf (unique + file_name_len, (1024 - file_name_len), L"_%i", ++id);
			// debug( "%ls", unique );
			ve = aafi->Video->Essences; // check again
		}
	}

	videoEssence->unique_file_name = unique;

	// debug( "%ls", videoEssence->wunique_file_name );

	return unique;
}

static aafObject*
get_Object_Ancestor (AAF_Iface* aafi, aafObject* Obj, const aafUID_t* ClassID)
{
	(void)aafi;

	/*
	 * NOTE : AAFClassID_ContentStorage is the container of Mob and EssenceData,
	 * not of Identification, Dictionary and MetaDictionary. If needed, the func
	 * should work for them too thanks to Obj != NULL.
	 */

	for (; Obj != NULL && aafUIDCmp (Obj->Class->ID, &AAFClassID_ContentStorage) == 0; Obj = Obj->Parent) {
		if (aafUIDCmp (Obj->Class->ID, ClassID))
			return Obj;
		/* Also work with abstract class */
		else if (aafUIDCmp (ClassID, &AAFClassID_Mob) && (aafUIDCmp (Obj->Class->ID, &AAFClassID_CompositionMob) || aafUIDCmp (Obj->Class->ID, &AAFClassID_MasterMob) || aafUIDCmp (Obj->Class->ID, &AAFClassID_SourceMob)))
			return Obj;
		else if (aafUIDCmp (ClassID, &AAFClassID_MobSlot) && (aafUIDCmp (Obj->Class->ID, &AAFClassID_TimelineMobSlot) || aafUIDCmp (Obj->Class->ID, &AAFClassID_StaticMobSlot) || aafUIDCmp (Obj->Class->ID, &AAFClassID_EventMobSlot)))
			return Obj;
	}

	return NULL;
}

/* ****************************************************************************
 *                           D i c t i o n a r y
 * ****************************************************************************/

static aafUID_t*
get_Component_DataDefinition (AAF_Iface* aafi, aafObject* Component)
{
	aafWeakRef_t* weakRef = aaf_get_propertyValue (Component, PID_Component_DataDefinition, &AAFTypeID_DataDefinitionWeakReference);

	if (weakRef == NULL) {
		warning ("Missing Component::DataDefinition.");
		return NULL;
	}

	aafObject* DataDefinition = aaf_get_ObjectByWeakRef (aafi->aafd->DataDefinition, weakRef);

	if (DataDefinition == NULL) {
		warning ("Could not retrieve WeakRef from Dictionary::DataDefinition.");
		return NULL;
	}

	aafUID_t* DataIdentification = aaf_get_propertyValue (DataDefinition, PID_DefinitionObject_Identification, &AAFTypeID_AUID);

	if (DataIdentification == NULL) {
		warning ("Missing DataDefinition's DefinitionObject::Identification.");
		return NULL;
	}

	return DataIdentification;
}

// static aafUID_t * get_FileDescriptor_ContainerFormat( AAF_Iface *aafi, aafObject *FileDescriptor )
// {
// 	aafWeakRef_t *ContainerDefWeakRef = aaf_get_propertyValue( FileDescriptor, PID_FileDescriptor_ContainerFormat );
//
// 	if ( ContainerDefWeakRef == NULL ) {
// 		warning( "Missing FileDescriptor::ContainerFormat." );
// 		return NULL;
// 	}
//
// 	aafObject *ContainerDefinition = aaf_get_ObjectByWeakRef( aafi->aafd->ContainerDefinition, ContainerDefWeakRef );
//
// 	if ( ContainerDefinition == NULL ) {
// 		warning( "Could not retrieve WeakRef from Dictionary::ContainerDefinitions." );
// 		return NULL;
// 	}
//
//
// 	aafUID_t *ContainerIdentification = aaf_get_propertyValue( ContainerDefinition, PID_DefinitionObject_Identification );
//
// 	if ( ContainerIdentification == NULL ) {
// 		warning( "Missing ContainerDefinition's DefinitionObject::Identification." );
// 		return NULL;
// 	}
//
//
// 	return ContainerIdentification;
// }

static aafUID_t*
get_OperationGroup_OperationIdentification (AAF_Iface* aafi, aafObject* OperationGroup)
{
	aafWeakRef_t* OperationDefWeakRef = aaf_get_propertyValue (OperationGroup, PID_OperationGroup_Operation, &AAFTypeID_OperationDefinitionWeakReference);

	if (OperationDefWeakRef == NULL) {
		error ("Missing OperationGroup::Operation.");
		return NULL;
	}

	aafObject* OperationDefinition = aaf_get_ObjectByWeakRef (aafi->aafd->OperationDefinition, OperationDefWeakRef);

	if (OperationDefinition == NULL) {
		error ("Could not retrieve OperationDefinition from dictionary.");
		return NULL;
	}

	aafUID_t* OperationIdentification = aaf_get_propertyValue (OperationDefinition, PID_DefinitionObject_Identification, &AAFTypeID_AUID);

	if (OperationIdentification == NULL) {
		error ("Missing DefinitionObject::Identification.");
		return NULL;
	}

	return OperationIdentification;
}

/* TODO not parameter ? VaryingValue ? */
static aafUID_t*
get_Parameter_InterpolationIdentification (AAF_Iface* aafi, aafObject* Parameter)
{
	aafWeakRef_t* InterpolationDefWeakRef = aaf_get_propertyValue (Parameter, PID_VaryingValue_Interpolation, &AAFTypeID_InterpolationDefinitionWeakReference);

	if (InterpolationDefWeakRef == NULL) {
		error ("Missing Parameter::Interpolation.");
		return NULL;
	}

	aafObject* InterpolationDefinition = aaf_get_ObjectByWeakRef (aafi->aafd->InterpolationDefinition, InterpolationDefWeakRef);

	if (InterpolationDefinition == NULL) {
		error ("Could not find InterpolationDefinition.");
		return NULL;
	}

	aafUID_t* InterpolationIdentification = aaf_get_propertyValue (InterpolationDefinition, PID_DefinitionObject_Identification, &AAFTypeID_AUID);

	if (InterpolationIdentification == NULL) {
		error ("Missing Parameter DefinitionObject::Identification.");
		return NULL;
	}

	return InterpolationIdentification;
}

static aafObject*
get_EssenceData_By_MobID (AAF_Iface* aafi, aafMobID_t* MobID)
{
	aafMobID_t* DataMobID   = NULL;
	aafObject*  EssenceData = NULL;

	for (EssenceData = aafi->aafd->EssenceData; EssenceData != NULL; EssenceData = EssenceData->next) {
		DataMobID = aaf_get_propertyValue (EssenceData, PID_EssenceData_MobID, &AAFTypeID_MobIDType);

		if (aafMobIDCmp (DataMobID, MobID))
			break;
	}

	return EssenceData;
}

// /* TODO is this SourceMobID or SourceID (masterMobID) ??? */
// static aafiAudioEssence * getAudioEssenceBySourceMobID( AAF_Iface *aafi, aafMobID_t *sourceMobID )
// {
// 	aafiAudioEssence * audioEssence = NULL;
//
//
// 	for ( audioEssence = aafi->Audio->Essences; audioEssence != NULL; audioEssence = audioEssence->next ) {
// 		if ( aafMobIDCmp( audioEssence->masterMobID, sourceMobID ) )
// 			break;
// 	}
//
//
// 	return audioEssence;
// }

// /* TODO is this SourceMobID or SourceID (masterMobID) ??? */
// static aafiVideoEssence * getVideoEssenceBySourceMobID( AAF_Iface *aafi, aafMobID_t *sourceMobID )
// {
// 	aafiVideoEssence * videoEssence = NULL;
//
// 	// debug( "%p", aafi->Video->tc );
// 	debug( "%p", aafi->Video->Essences );
// 	debug( "%ls", aaft_MobIDToText( sourceMobID ) );
//
//
// 	for ( videoEssence = aafi->Video->Essences; videoEssence != NULL; videoEssence = videoEssence->next ) {
// 		if ( aafMobIDCmp( videoEssence->masterMobID, sourceMobID ) )
// 			break;
// 	}
//
//
// 	return videoEssence;
// }

/* ****************************************************************************
 *                    E s s e n c e D e s c r i p t o r
 * ****************************************************************************
 *
 *  EssenceDescriptor (abs)
 *          |
 *          |--> FileDescriptor (abs)
 *          |          |
 *          |          |--> WAVEDescriptor
 *          |          |--> AIFCDescriptor
 *          |          |--> SoundDescriptor
 *          |          |          |
 *          |          |          `--> PCMDescriptor
 *          |          |
 *          |          `--> DigitalImageDescriptor (abs)
 *          |                     |
 *          |                     `--> CDCIDescriptor
 *          |
 *          |
 *          |--> PhysicalDescriptor
 *          `--> TapeDescriptor
 */

static int
parse_EssenceDescriptor (AAF_Iface* aafi, aafObject* EssenceDesc, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_PCMDescriptor)) {
		parse_PCMDescriptor (aafi, EssenceDesc, &__td);
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_WAVEDescriptor)) {
		parse_WAVEDescriptor (aafi, EssenceDesc, &__td);
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_AIFCDescriptor)) {
		parse_AIFCDescriptor (aafi, EssenceDesc, &__td);
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_SoundDescriptor)) {
		/* Compressed Audio (MP3, AAC ?). Not encountered yet */

		__td.lv++;
		DUMP_OBJ_NO_SUPPORT (aafi, EssenceDesc, &__td);
		__td.lv--;
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_AES3PCMDescriptor)) {
		/* Not described in specs, not encountered yet. */

		__td.lv++;
		DUMP_OBJ_NO_SUPPORT (aafi, EssenceDesc, &__td);
		__td.lv--;
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_MultipleDescriptor)) {
		/*
		 * A MultipleDescriptor contains a vector of FileDescriptor objects and is
		 * used when the file source consists of multiple tracks of essence (e.g MXF).
		 * Each essence track is described by a MobSlots object in the SourceMob and a
		 * FileDescriptor object. The FileDescriptor is linked to the MobSlot by
		 * setting the FileDescriptor::LinkedSlotID property equal to the
		 * MobSlot::SlotID property.
		 *
		 * -> test.aaf
		 */

		__td.lv++;
		DUMP_OBJ_NO_SUPPORT (aafi, EssenceDesc, &__td);
		__td.lv--;

	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_CDCIDescriptor)) {
		parse_CDCIDescriptor (aafi, EssenceDesc, &__td);
	} else {
		__td.lv++;
		DUMP_OBJ_NO_SUPPORT (aafi, EssenceDesc, &__td);
		__td.lv--;
	}

	/*
	 * Locators are a property of EssenceDescriptor. The property holds a vector of
	 * Locators object, that should provide information to help find a file that
	 * contains the essence (WAV, MXF, etc.) or to help find the physical media.
	 *
	 * A Locator can either be a NetworkLocator or a TextLocator.
	 *
	 * A NetworkLocator holds a URLString property :
	 *
	 * p.41 : Absolute Uniform Resource Locator (URL) complying with RFC 1738 or relative
	 * Uniform Resource Identifier (URI) complying with RFC 2396 for file containing
	 * the essence. If it is a relative URI, the base URI is determined from the URI
	 * of the AAF file itself.
	 * Informative note: A valid URL or URI uses a constrained character set and uses
	 * the / character as the path separator.
	 */

	aafObject* Locator  = NULL;
	aafObject* Locators = aaf_get_propertyValue (EssenceDesc, PID_EssenceDescriptor_Locator, &AAFTypeID_LocatorStrongReferenceVector); /* opt */

	__td.lv++;
	int i = 0;

	aaf_foreach_ObjectInSet (&Locator, Locators, NULL)
	{
		/* TODO retrieve all locators, then when searching file, try all parsed locators. */
		__td.ll[__td.lv] = (Locators->Header->_entryCount > 1) ? (Locators->Header->_entryCount - i++) : 0;
		parse_Locator (aafi, Locator, &__td);
	}

	return 0;
}

static int
parse_DigitalImageDescriptor (AAF_Iface* aafi, aafObject* DIDescriptor, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	/* TODO parse and save content to videoEssence */

	aafiVideoEssence* videoEssence = aafi->ctx.current_video_essence; //(aafiVideoEssence*)aafi->ctx.current_essence;

	if (videoEssence == NULL) {
		DUMP_OBJ_ERROR (aafi, DIDescriptor, &__td, "aafi->ctx.current_video_essence not set");
		return -1;
	}

	aafRational_t* framerate = aaf_get_propertyValue (DIDescriptor, PID_FileDescriptor_SampleRate, &AAFTypeID_Rational);

	if (framerate == NULL) {
		DUMP_OBJ_ERROR (aafi, DIDescriptor, &__td, "Missing PID_FileDescriptor_SampleRate");
		return -1;
	}

	videoEssence->framerate = framerate;

	uint32_t* storedHeight = aaf_get_propertyValue (DIDescriptor, PID_DigitalImageDescriptor_StoredHeight, &AAFTypeID_UInt32);

	if (storedHeight == NULL) {
		DUMP_OBJ_ERROR (aafi, DIDescriptor, &__td, "Missing PID_DigitalImageDescriptor_StoredHeight");
		return -1;
	}

	// debug( "storedHeight : %u", *storedHeight );

	uint32_t* storedWidth = aaf_get_propertyValue (DIDescriptor, PID_DigitalImageDescriptor_StoredWidth, &AAFTypeID_UInt32);

	if (storedWidth == NULL) {
		DUMP_OBJ_ERROR (aafi, DIDescriptor, &__td, "Missing PID_DigitalImageDescriptor_StoredWidth");
		return -1;
	}

	// debug( "storedWidth : %u", *storedWidth );

	uint32_t* displayHeight = aaf_get_propertyValue (DIDescriptor, PID_DigitalImageDescriptor_DisplayHeight, &AAFTypeID_UInt32);

	if (displayHeight == NULL) {
		DUMP_OBJ_ERROR (aafi, DIDescriptor, &__td, "Missing PID_DigitalImageDescriptor_DisplayHeight");
		return -1;
	}

	// debug( "displayHeight : %u", *displayHeight );

	uint32_t* displayWidth = aaf_get_propertyValue (DIDescriptor, PID_DigitalImageDescriptor_DisplayWidth, &AAFTypeID_UInt32);

	if (displayWidth == NULL) {
		DUMP_OBJ_ERROR (aafi, DIDescriptor, &__td, "Missing PID_DigitalImageDescriptor_DisplayWidth");
		return -1;
	}

	// debug( "displayWidth : %u", *displayWidth );

	aafRational_t* imageAspectRatio = aaf_get_propertyValue (DIDescriptor, PID_DigitalImageDescriptor_ImageAspectRatio, &AAFTypeID_Rational);

	if (imageAspectRatio == NULL) {
		DUMP_OBJ_ERROR (aafi, DIDescriptor, &__td, "Missing PID_DigitalImageDescriptor_ImageAspectRatio");
		return -1;
	}

	// debug( "imageAspectRatio : %i/%i", imageAspectRatio->numerator, imageAspectRatio->denominator );

	return 0;
}

static int
parse_CDCIDescriptor (AAF_Iface* aafi, aafObject* CDCIDescriptor, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	if (!aaf_get_property (CDCIDescriptor, PID_EssenceDescriptor_Locator))
		__td.eob = 1;

	/* TODO parse CDCI class */

	int rc = parse_DigitalImageDescriptor (aafi, CDCIDescriptor, __ptd);

	if (!rc)
		DUMP_OBJ (aafi, CDCIDescriptor, &__td);

	return rc;
}

static int
parse_PCMDescriptor (AAF_Iface* aafi, aafObject* PCMDescriptor, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	if (!aaf_get_property (PCMDescriptor, PID_EssenceDescriptor_Locator))
		__td.eob = 1;

	aafiAudioEssence* audioEssence = (aafiAudioEssence*)aafi->ctx.current_essence;

	if (audioEssence == NULL) {
		DUMP_OBJ_ERROR (aafi, PCMDescriptor, &__td, "aafi->ctx.current_essence not set");
		return -1;
	}

	audioEssence->type = AAFI_ESSENCE_TYPE_PCM;

	/* Duration of the essence in sample units (not edit units !) */
	aafPosition_t* length = aaf_get_propertyValue (PCMDescriptor, PID_FileDescriptor_Length, &AAFTypeID_PositionType);

	if (length == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, PCMDescriptor, &__td, "Missing PID_FileDescriptor_Length");
		return -1;
	}

	audioEssence->length = *length;

	uint32_t* channels = aaf_get_propertyValue (PCMDescriptor, PID_SoundDescriptor_Channels, &AAFTypeID_UInt32);

	if (channels == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, PCMDescriptor, &__td, "Missing PID_SoundDescriptor_Channels");
		return -1;
	}

	audioEssence->channels = *channels;

	aafRational_t* samplerate = aaf_get_propertyValue (PCMDescriptor, PID_FileDescriptor_SampleRate, &AAFTypeID_Rational);

	if (samplerate == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, PCMDescriptor, &__td, "Missing PID_FileDescriptor_SampleRate");
		return -1;
	}

	if (samplerate->denominator != 1) {
		DUMP_OBJ_ERROR (aafi, PCMDescriptor, &__td, "PID_FileDescriptor_SampleRate should be integer but is %i/%i", samplerate->numerator, samplerate->denominator);
		return -1;
	}

	audioEssence->samplerate = samplerate->numerator;

	// if ( aafi->Audio->samplerate >= 0 ) {
	/* Set global AAF SampleRate, if it equals preceding. Otherwise set to -1 */
	// aafi->Audio->samplerate = ( aafi->Audio->samplerate == 0 || aafi->Audio->samplerate == *samplerate ) ? *samplerate : (unsigned)-1;
	// }

	uint32_t* samplesize = aaf_get_propertyValue (PCMDescriptor, PID_SoundDescriptor_QuantizationBits, &AAFTypeID_UInt32); // uint32_t in AAF std

	if (samplesize == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, PCMDescriptor, &__td, "Missing PID_SoundDescriptor_QuantizationBits");
		return -1;
	}

	if (*samplesize >= (1 << 15)) {
		DUMP_OBJ_ERROR (aafi, PCMDescriptor, &__td, "PID_SoundDescriptor_QuantizationBits value error : %u", *samplesize);
		return -1;
	}

	audioEssence->samplesize = (int16_t)*samplesize;

	if (aafi->Audio->samplesize >= 0) {
		/* Set global AAF SampleSize, if it equals preceding. Otherwise set to -1 */
		aafi->Audio->samplesize = (aafi->Audio->samplesize == 0 || (uint16_t)aafi->Audio->samplesize == audioEssence->samplesize) ? audioEssence->samplesize : -1;
	}

	/* TODO parse the rest of the class */

	DUMP_OBJ (aafi, PCMDescriptor, &__td);

	return 0;
}

static int
parse_WAVEDescriptor (AAF_Iface* aafi, aafObject* WAVEDescriptor, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	if (!aaf_get_property (WAVEDescriptor, PID_EssenceDescriptor_Locator))
		__td.eob = 1;

	aafiAudioEssence* audioEssence = (aafiAudioEssence*)aafi->ctx.current_essence;

	if (audioEssence == NULL) {
		DUMP_OBJ_ERROR (aafi, WAVEDescriptor, &__td, "aafi->ctx.current_essence not set");
		return -1;
	}

	audioEssence->type = AAFI_ESSENCE_TYPE_WAVE;

	aafProperty* summary = aaf_get_property (WAVEDescriptor, PID_WAVEDescriptor_Summary);

	if (summary == NULL) {
		DUMP_OBJ_ERROR (aafi, WAVEDescriptor, &__td, "Missing PID_WAVEDescriptor_Summary");
		return -1;
	}

	audioEssence->summary = summary;

	/*
	 * NOTE : Summary is parsed later in "post-processing" aafi_retrieveData(),
	 * to be sure clips and essences are linked, so we are able to fallback on
	 * essence stream in case summary does not contain the full header part.
	 *
	 * TODO parse it here
	 */

	DUMP_OBJ (aafi, WAVEDescriptor, &__td);

	return 0;
}

static int
parse_AIFCDescriptor (AAF_Iface* aafi, aafObject* AIFCDescriptor, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	if (!aaf_get_property (AIFCDescriptor, PID_EssenceDescriptor_Locator))
		__td.eob = 1;

	aafiAudioEssence* audioEssence = (aafiAudioEssence*)aafi->ctx.current_essence;

	if (audioEssence == NULL) {
		DUMP_OBJ_ERROR (aafi, AIFCDescriptor, &__td, "aafi->ctx.current_essence not set");
		return -1;
	}

	audioEssence->type = AAFI_ESSENCE_TYPE_AIFC;

	aafProperty* summary = aaf_get_property (AIFCDescriptor, PID_AIFCDescriptor_Summary);

	if (summary == NULL) {
		DUMP_OBJ_ERROR (aafi, AIFCDescriptor, &__td, "Missing PID_AIFCDescriptor_Summary");
		return -1;
	}

	audioEssence->summary = summary;

	/*
	 * NOTE : Summary is parsed later in "post-processing" aafi_retrieveData(),
	 * to be sure clips and essences are linked, so we are able to fallback on
	 * essence stream in case summary does not contain the full header part.
	 */

	DUMP_OBJ (aafi, AIFCDescriptor, &__td);

	return 0;
}

/*
 *            Locator (abs)
 *               |
 *       ,---------------.
 *       |               |
 * NetworkLocator   TextLocator
 */

static int
parse_Locator (AAF_Iface* aafi, aafObject* Locator, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	if (aafUIDCmp (Locator->Class->ID, &AAFClassID_NetworkLocator)) {
		parse_NetworkLocator (aafi, Locator, &__td);
	} else if (aafUIDCmp (Locator->Class->ID, &AAFClassID_TextLocator)) {
		/*
		 * A TextLocator object provides information to the user to help locate the file
		 * containing the essence or to locate the physical media. The TextLocator is not
		 * intended for applications to use without user intervention.
		 *
		 * TODO what to do with those ???
		 *       never encountered anyway..
		 */

		__td.eob = 1;
		__td.lv++;
		DUMP_OBJ_NO_SUPPORT (aafi, Locator, &__td);

		// wchar_t *name = aaf_get_propertyValue( Locator, PID_TextLocator_Name, &AAFTypeID_String );
		// warning( "Got an AAFClassID_TextLocator : \"%ls\"", name );
		// free( name );
	} else {
		__td.eob = 1;
		__td.lv++;
		DUMP_OBJ_NO_SUPPORT (aafi, Locator, &__td);
	}

	return 0;
}

static int
parse_NetworkLocator (AAF_Iface* aafi, aafObject* NetworkLocator, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);
	__td.eob = 1;

	/*
	 * This holds an URI pointing to the essence file, when it is not embedded.
	 * However, sometimes it holds an URI to the AAF file itself when essence is
	 * embedded so it is not a valid way to test if essence is embedded or not.
	 */

	wchar_t* original_file_path = aaf_get_propertyValue (NetworkLocator, PID_NetworkLocator_URLString, &AAFTypeID_String);

	if (original_file_path == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, NetworkLocator, &__td, "Missing PID_NetworkLocator_URLString");
		return -1;
	}

	// uriDecodeWString( original_file_path, NULL );
	// wurl_decode( original_file_path, original_file_path ); // TODO : What about URIParser lib ?!

	/* TODO find a better way to check if we're parsing audio */

	if (aafi->ctx.current_essence) {
		aafi->ctx.current_essence->original_file_path = original_file_path;
	} else if (aafi->ctx.current_video_essence) {
		aafi->ctx.current_video_essence->original_file_path = original_file_path;
	} else {
		DUMP_OBJ_ERROR (aafi, NetworkLocator, &__td, "aafi->ctx.current_essence AND aafi->ctx.current_video_essence not set");
		return -1;
	}

	DUMP_OBJ_INFO (aafi, NetworkLocator, &__td, ": %ls", original_file_path);

	return 0;
}

static int
parse_EssenceData (AAF_Iface* aafi, aafObject* EssenceData, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);
	__td.eob = 1;

	aafiAudioEssence* audioEssence = (aafiAudioEssence*)aafi->ctx.current_essence;

	if (audioEssence == NULL) {
		DUMP_OBJ_ERROR (aafi, EssenceData, &__td, "aafi->ctx.current_essence not set");
		return -1;
	}

	/*
	 * The EssenceData::Data property has the stored form SF_DATA_STREAM, so
	 * it holds the name of the Data stream, which should be located at
	 * /Path/To/EssenceData/DataStream
	 */

	wchar_t* StreamName = aaf_get_propertyValue (EssenceData, PID_EssenceData_Data, &AAFTypeID_String);

	if (StreamName == NULL) {
		DUMP_OBJ_ERROR (aafi, EssenceData, &__td, "Missing PID_EssenceData_Data");
		return -1;
	}

	wchar_t DataPath[CFB_PATH_NAME_SZ];
	memset (DataPath, 0x00, sizeof (DataPath));

	wchar_t* path = aaf_get_ObjectPath (EssenceData);

	swprintf (DataPath, CFB_PATH_NAME_SZ, L"%" WPRIws L"/%" WPRIws, path, StreamName);

	free (StreamName);

	cfbNode* DataNode = cfb_getNodeByPath (aafi->aafd->cfbd, DataPath, 0);

	if (DataNode == NULL) {
		DUMP_OBJ_ERROR (aafi, EssenceData, &__td, "Could not retrieve Data stream node %ls", DataPath);
		return -1;
	}

	audioEssence->node = DataNode;

	audioEssence->is_embedded = 1; /* TODO to be set elsewhere ? */

	/* disable raw data byte length, because we want it to be the exact audio length in samples */

	// uint64_t dataLen = cfb_getNodeStreamLen( aafi->aafd->cfbd, DataNode );
	//
	// if ( dataLen == 0 ) {
	// 	DUMP_OBJ_WARNING( aafi, EssenceData, &__td, "Got 0 Bytes Data stream length" );
	// 	return -1;
	// }
	// else {
	// 	DUMP_OBJ( aafi, EssenceData, &__td );
	// }
	//
	// /* NOTE Might be tweaked by aafi_parse_audio_summary() */
	// audioEssence->length = dataLen;

	return 0;
}

/* ****************************************************************************
 *                           C o m p o n e n t
 * ****************************************************************************
 *
 *                     Component (abs)
 *                          |
 *                    ,-----------.
 *                    |           |
 *               Transition    Segment (abs)
 *                                |
 *                                |--> Sequence
 *                                |--> Filler
 *                                |--> TimeCode
 *                                |--> OperationGroup
 *                                `--> SourceReference (abs)
 *                                            |
 *                                            `--> SourceClip
 */

static int
parse_Component (AAF_Iface* aafi, aafObject* Component, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	if (aafUIDCmp (Component->Class->ID, &AAFClassID_Transition)) {
		/*
		 * A Transition between a Filler and a SourceClip sets a Fade In.
		 * A Transition between a SourceClip and a Filler sets a Fade Out.
		 * A Transition between two SourceClips sets a Cross-Fade.
		 *
		 * Since the Transition applies to the elements that suround it in
		 * the Sequence, the OperationGroup::InputSegments is then left unused.
		 */

		parse_Transition (aafi, Component, &__td);
	} else {
		aafi_parse_Segment (aafi, Component, &__td);
	}

	return 0;
}

static int
parse_Transition (AAF_Iface* aafi, aafObject* Transition, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	aafUID_t* DataDefinition = get_Component_DataDefinition (aafi, Transition);

	if (DataDefinition == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, Transition, &__td, "Could not retrieve DataDefinition");
		return -1;
	}

	if (!aafUIDCmp (DataDefinition, &AAFDataDef_Sound) &&
	    !aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
		DUMP_OBJ_ERROR (aafi, Transition, &__td, "Current implementation only supports Transition inside Audio Tracks");
		return -1;
	}

	int64_t* length = aaf_get_propertyValue (Transition, PID_Component_Length, &AAFTypeID_LengthType);

	if (length == NULL) {
		DUMP_OBJ_ERROR (aafi, Transition, &__td, "Missing PID_Component_Length");
		return -1;
	}

	int flags = 0;

	if (Transition->prev != NULL && aafUIDCmp (Transition->prev->Class->ID, &AAFClassID_Filler)) {
		flags |= AAFI_TRANS_FADE_IN;
	} else if (Transition->next != NULL && aafUIDCmp (Transition->next->Class->ID, &AAFClassID_Filler)) {
		flags |= AAFI_TRANS_FADE_OUT;
	} else if (Transition->next != NULL && aafUIDCmp (Transition->next->Class->ID, &AAFClassID_Filler) == 0 &&
	           Transition->prev != NULL && aafUIDCmp (Transition->prev->Class->ID, &AAFClassID_Filler) == 0) {
		flags |= AAFI_TRANS_XFADE;
	} else {
		DUMP_OBJ_ERROR (aafi, Transition, &__td, "Could not guess if type is FadeIn, FadeOut or xFade");
		return -1;
	}

	aafiTimelineItem* Item = aafi_newTimelineItem (aafi, aafi->ctx.current_track, AAFI_TRANS);

	aafiTransition* Trans = Item->data; //(aafiTransition*)&Item->data;

	Trans->len   = *length;
	Trans->flags = flags;

	int missing_cutpt = 0;

	aafPosition_t* cut_point = aaf_get_propertyValue (Transition, PID_Transition_CutPoint, &AAFTypeID_PositionType);

	if (cut_point == NULL) { /* req */
		// DUMP_OBJ_WARNING( aafi, Transition, &__td, "Missing PID_Transition_CutPoint : Setting to Trans->len/2" );
		missing_cutpt = 1;
		Trans->cut_pt = Trans->len / 2; // set default cutpoint to the middle of transition
	} else {
		Trans->cut_pt = *cut_point;
	}

	aafObject* OpGroup = aaf_get_propertyValue (Transition, PID_Transition_OperationGroup, &AAFTypeID_OperationGroupStrongReference);

	if (OpGroup != NULL) { /* req */

		if (missing_cutpt) {
			DUMP_OBJ_WARNING (aafi, Transition, &__td, "Missing PID_Transition_CutPoint : Setting to Trans->len/2");
		} else {
			DUMP_OBJ (aafi, Transition, &__td);
		}

		/*
		 * Don't handle parse_OperationGroup() return code, since it should
		 * always fallback to default in case of failure.
		 */

		aafi->ctx.current_transition = Trans;
		parse_OperationGroup (aafi, OpGroup, &__td);
		aafi->ctx.current_transition = NULL;
	} else {
		/* Setting fade to default */

		__td.eob = 1;

		if (missing_cutpt) {
			DUMP_OBJ_WARNING (aafi, Transition, &__td, "Missing PID_Transition_CutPoint AND PID_Transition_OperationGroup : Setting to Trans->len/2; Linear");
		} else {
			DUMP_OBJ_WARNING (aafi, Transition, &__td, "Missing PID_Transition_OperationGroup : Setting to Linear interpolation");
		}

		Trans->flags |= (AAFI_INTERPOL_LINEAR | AAFI_TRANS_SINGLE_CURVE);

		Trans->time_a  = calloc (2, sizeof (aafRational_t));
		Trans->value_a = calloc (2, sizeof (aafRational_t));

		Trans->time_a[0].numerator   = 0;
		Trans->time_a[0].denominator = 0;
		Trans->time_a[1].numerator   = 1;
		Trans->time_a[1].denominator = 1;

		if (Trans->flags & AAFI_TRANS_FADE_IN ||
		    Trans->flags & AAFI_TRANS_XFADE) {
			Trans->value_a[0].numerator   = 0;
			Trans->value_a[0].denominator = 0;
			Trans->value_a[1].numerator   = 1;
			Trans->value_a[1].denominator = 1;
		} else if (Trans->flags & AAFI_TRANS_FADE_OUT) {
			Trans->value_a[0].numerator   = 1;
			Trans->value_a[0].denominator = 1;
			Trans->value_a[1].numerator   = 0;
			Trans->value_a[1].denominator = 0;
		}
	}

	aafi->ctx.current_track->current_pos -= *length;

	return 0;
}

static int
parse_NestedScope (AAF_Iface* aafi, aafObject* NestedScope, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	aafObject* Slot  = NULL;
	aafObject* Slots = aaf_get_propertyValue (NestedScope, PID_NestedScope_Slots, &AAFUID_NULL);

	if (Slots == NULL) {
		DUMP_OBJ_ERROR (aafi, NestedScope, &__td, "Missing PID_NestedScope_Slots");
		return -1;
	}

	DUMP_OBJ (aafi, NestedScope, &__td);

	int i = 0;

	aaf_foreach_ObjectInSet (&Slot, Slots, NULL)
	{
		__td.ll[__td.lv] = (Slots->Header->_entryCount > 1) ? (Slots->Header->_entryCount - i++) : 0; //(MobSlot->next) ? 1 : 0;
		aafi_parse_Segment (aafi, Slot, &__td);
	}

	/* TODO should we take aafi_parse_Segment() return code into account ? */

	return 0;
}

int
aafi_parse_Segment (AAF_Iface* aafi, aafObject* Segment, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	if (aafUIDCmp (Segment->Class->ID, &AAFClassID_Sequence)) {
		return parse_Sequence (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_SourceClip)) {
		return parse_SourceClip (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_OperationGroup)) {
		return parse_OperationGroup (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_Filler)) {
		return parse_Filler (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_Selector)) {
		return parse_Selector (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_NestedScope)) {
		return parse_NestedScope (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_Timecode)) {
		/*
		 * TODO can contain sequence ? other Timecode SMPTE ..
		 */

		return parse_Timecode (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_DescriptiveMarker)) {
		if (resolve_AAF (aafi)) {
			resolve_parse_aafObject_DescriptiveMarker (aafi, Segment, &__td);
		} else {
			__td.lv++;
			DUMP_OBJ_NO_SUPPORT (aafi, Segment, &__td);
			return -1;
		}
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_EssenceGroup)) {
		/*
		 * Should provide support for multiple essences representing the same
		 * source material with different resolution, compression, codec, etc.
		 *
		 * TODO To be tested with Avid and rendered effects.
		 */

		__td.lv++;
		DUMP_OBJ_NO_SUPPORT (aafi, Segment, &__td);
		return -1;
	} else {
		__td.lv++;
		DUMP_OBJ_NO_SUPPORT (aafi, Segment, &__td);
	}

	return 0;
}

static int
parse_Filler (AAF_Iface* aafi, aafObject* Filler, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	__td.eob = 1;

	aafUID_t* DataDefinition = get_Component_DataDefinition (aafi, Filler);

	if (DataDefinition == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, Filler, &__td, "Could not retrieve DataDefinition");
		return -1;
	}

	if (aafUIDCmp (Filler->Parent->Class->ID, &AAFClassID_TimelineMobSlot)) {
		/*
		 * Just an empty track, do nothing.
		 */
	} else if (aafUIDCmp (Filler->Parent->Class->ID, &AAFClassID_Sequence) ||
	           aafUIDCmp (Filler->Parent->Class->ID, &AAFClassID_Selector)) {
		/*
		 * This represents an empty space on the timeline, between two clips
		 * which is Component::Length long.
		 */

		int64_t* length = aaf_get_propertyValue (Filler, PID_Component_Length, &AAFTypeID_LengthType);

		if (length == NULL) { /* probably req for Filler */
			DUMP_OBJ_ERROR (aafi, Filler, &__td, "Missing PID_Component_Length");
			return -1;
		}

		if (aafUIDCmp (DataDefinition, &AAFDataDef_Sound) ||
		    aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
			aafi->ctx.current_track->current_pos += *length;
		} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Picture) ||
		           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyPicture)) {
			aafi->Video->Tracks->current_pos += *length;
		}

	} else {
		DUMP_OBJ_NO_SUPPORT (aafi, Filler, &__td);
		return -1;
	}

	DUMP_OBJ (aafi, Filler, &__td);

	return 0;
}

static int
parse_Sequence (AAF_Iface* aafi, aafObject* Sequence, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	aafObject* Component  = NULL;
	aafObject* Components = aaf_get_propertyValue (Sequence, PID_Sequence_Components, &AAFTypeID_ComponentStrongReferenceVector);

	if (Components == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, Sequence, &__td, "Missing PID_Sequence_Components");
		return -1;
	}

	DUMP_OBJ (aafi, Sequence, &__td);

	int i = 0;

	aaf_foreach_ObjectInSet (&Component, Components, NULL)
	{
		__td.ll[__td.lv] = (Components->Header->_entryCount > 1) ? (Components->Header->_entryCount - i++) : 0; //(MobSlot->next) ? 1 : 0;
		parse_Component (aafi, Component, &__td);
	}

	return 0;
}

static int
parse_Timecode (AAF_Iface* aafi, aafObject* Timecode, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	__td.eob = 1;

	aafPosition_t* tc_start = aaf_get_propertyValue (Timecode, PID_Timecode_Start, &AAFTypeID_PositionType);

	if (tc_start == NULL) {
		DUMP_OBJ_ERROR (aafi, Timecode, &__td, "Missing PID_Timecode_Start");
		return -1;
	}

	uint16_t* tc_fps = aaf_get_propertyValue (Timecode, PID_Timecode_FPS, &AAFTypeID_UInt16);

	if (tc_fps == NULL) {
		DUMP_OBJ_ERROR (aafi, Timecode, &__td, "Missing PID_Timecode_FPS");
		return -1;
	}

	uint8_t* tc_drop = aaf_get_propertyValue (Timecode, PID_Timecode_Drop, &AAFTypeID_UInt8);

	if (tc_drop == NULL) {
		DUMP_OBJ_ERROR (aafi, Timecode, &__td, "Missing PID_Timecode_Drop");
		return -1;
	}

	/* TODO this should be retrieved directly from TimelineMobSlot */

	aafObject* ParentMobSlot = get_Object_Ancestor (aafi, Timecode, &AAFClassID_MobSlot);

	if (ParentMobSlot == NULL) {
		DUMP_OBJ_ERROR (aafi, Timecode, &__td, "Could not retrieve parent MobSlot");
		return -1;
	}

	aafRational_t* tc_edit_rate = aaf_get_propertyValue (ParentMobSlot, PID_TimelineMobSlot_EditRate, &AAFTypeID_Rational);

	if (tc_edit_rate == NULL) {
		DUMP_OBJ_ERROR (aafi, Timecode, &__td, "Missing parent MobSlot PID_TimelineMobSlot_EditRate");
		return -1;
	}

	if (aafi->Timecode) {
		DUMP_OBJ_WARNING (aafi, Timecode, &__td, "Timecode was already set, ignoring (%lu, %u fps)", *tc_start, *tc_fps);
		return -1;
	}
	/* TODO allocate in specific function */

	aafiTimecode* tc = calloc (sizeof (aafiTimecode), sizeof (unsigned char));

	if (tc == NULL) {
		DUMP_OBJ_ERROR (aafi, Timecode, &__td, "calloc() : %s", strerror (errno));
		return -1;
	}

	tc->start     = *tc_start;
	tc->fps       = *tc_fps;
	tc->drop      = *tc_drop;
	tc->edit_rate = tc_edit_rate;

	aafi->Timecode = tc;

	DUMP_OBJ (aafi, Timecode, &__td);

	return 0;
}

static int
parse_OperationGroup (AAF_Iface* aafi, aafObject* OpGroup, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	if (!aaf_get_property (OpGroup, PID_OperationGroup_InputSegments) &&
	    !aaf_get_property (OpGroup, PID_OperationGroup_Parameters)) {
		__td.eob = 1;
	}

	aafObject* ParentMob = get_Object_Ancestor (aafi, OpGroup, &AAFClassID_Mob);

	if (ParentMob == NULL) {
		DUMP_OBJ_ERROR (aafi, OpGroup, &__td, "Could not retrieve parent Mob");
		return -1;
	}

	if (!aafUIDCmp (ParentMob->Class->ID, &AAFClassID_CompositionMob)) {
		DUMP_OBJ_ERROR (aafi, OpGroup, &__td, "OperationGroup parser is currently implemented for AAFClassID_CompositionMob children only");
		return -1;
	}

	aafUID_t* OperationIdentification = get_OperationGroup_OperationIdentification (aafi, OpGroup);

	/* PRINT OPERATIONDEFINITIONS */

	// aafObject * Parameters = aaf_get_propertyValue( OpGroup, PID_OperationGroup_Parameters );
	//
	// if ( Parameters ) {
	// 	aafObject * Param = NULL;
	//
	// 	aaf_foreach_ObjectInSet( &Param, Parameters, NULL ) {
	// 	 aafUID_t *ParamDef = aaf_get_propertyValue( Param, PID_Parameter_Definition, &AAFTypeID_AUID );
	// 	 debug( "     OpDef %ls     (%ls) | %ls", aaft_OperationDefToText(aafi->aafd, OperationIdentification), AUIDToText( ParamDef ), aaft_ParameterToText( aafi->aafd, ParamDef ) );
	// 	}
	// }

	// if ( aafUIDCmp( aafi->ctx.Mob->Class->ID, &AAFClassID_MasterMob ) ) {
	//
	// 	/*
	// 	 * TODO: This was seen in the spec, but never encountered in real world.
	// 	 */
	//
	// 	aafi_trace_obj( aafi, Segment, ANSI_COLOR_RED );
	// 	error( "MobSlot::Segment > OperationGroup Not implemented yet." );
	// 	return -1;
	//
	// }

	int rc = 0;

	if (aafUIDCmp (OpGroup->Parent->Class->ID, &AAFClassID_Transition)) {
		aafiTransition* Trans = aafi->ctx.current_transition;

		if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioDissolve)) {
			/*
			 * Mono Audio Dissolve (Fade, Cross Fade)
			 *
			 * The same parameter (curve/level) is applied to the outgoing fade on first
			 * clip (if any) and to the incoming fade on second clip (if any).
			 */

			// __td.eob = 1;

			Trans->flags |= AAFI_TRANS_SINGLE_CURVE;

			int set_default = 0;

			aafObject* Param      = NULL;
			aafObject* Parameters = aaf_get_propertyValue (OpGroup, PID_OperationGroup_Parameters, &AAFTypeID_ParameterStrongReferenceVector); /* opt */

			if (Parameters) {
				/* Retrieve AAFParameterDef_Level parameter */

				aaf_foreach_ObjectInSet (&Param, Parameters, NULL)
				{
					aafUID_t* ParamDef = aaf_get_propertyValue (Param, PID_Parameter_Definition, &AAFTypeID_AUID);

					if (aafUIDCmp (ParamDef, &AAFParameterDef_Level))
						break;
				}
			} else {
				// DUMP_OBJ_WARNING( aafi, OpGroup, &__td, "Missing PID_OperationGroup_Parameters: Falling back to Linear" );
				set_default = 1;
			}

			if (Param) {
				DUMP_OBJ (aafi, OpGroup, &__td);

				if (aaf_get_property (OpGroup, PID_OperationGroup_InputSegments)) {
					__td.ll[__td.lv] = 2;
				}

				if (parse_Parameter (aafi, Param, &__td) < 0) {
					set_default = 1;
				}

				__td.ll[__td.lv] = 0;
			} else {
				/*
				 * Do not notify exception since this case is standard compliant :
				 *
				 * ParameterDef_Level (optional; default is a VaryingValue object with
				 * two control points: Value 0 at time 0, and value 1 at time 1)
				 */

				__td.eob = 1;
				DUMP_OBJ (aafi, OpGroup, &__td);

				// DUMP_OBJ_WARNING( aafi, OpGroup, &__td, "Missing Parameter AAFParameterDef_Level: Setting to Linear" );

				set_default = 1;
			}

			if (set_default) {
				/*
				 * ParameterDef_Level (optional; default is a VaryingValue object
				 * with two control points: Value 0 at time 0, and value 1 at time 1)
				 *
				 * This is also a fallback in case of parse_Parameter() failure.
				 */

				Trans->flags |= AAFI_INTERPOL_LINEAR;

				Trans->time_a  = calloc (2, sizeof (aafRational_t));
				Trans->value_a = calloc (2, sizeof (aafRational_t));

				Trans->time_a[0].numerator   = 0;
				Trans->time_a[0].denominator = 0;
				Trans->time_a[1].numerator   = 1;
				Trans->time_a[1].denominator = 1;

				if (Trans->flags & AAFI_TRANS_FADE_IN ||
				    Trans->flags & AAFI_TRANS_XFADE) {
					Trans->value_a[0].numerator   = 0;
					Trans->value_a[0].denominator = 0;
					Trans->value_a[1].numerator   = 1;
					Trans->value_a[1].denominator = 1;
				} else if (Trans->flags & AAFI_TRANS_FADE_OUT) {
					Trans->value_a[0].numerator   = 1;
					Trans->value_a[0].denominator = 1;
					Trans->value_a[1].numerator   = 0;
					Trans->value_a[1].denominator = 0;
				}
			}

		} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_TwoParameterMonoAudioDissolve)) {
			DUMP_OBJ_NO_SUPPORT (aafi, OpGroup, &__td);
			/* Two distinct parameters are used for the outgoing and incoming fades. */
		} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_StereoAudioDissolve)) {
			DUMP_OBJ_NO_SUPPORT (aafi, OpGroup, &__td);
			/* TODO Unknown usage and implementation */
		} else {
			DUMP_OBJ_NO_SUPPORT (aafi, OpGroup, &__td);
		}

	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_AudioChannelCombiner)) {
		DUMP_OBJ (aafi, OpGroup, &__td);

		aafObject* InputSegment  = NULL;
		aafObject* InputSegments = aaf_get_propertyValue (OpGroup, PID_OperationGroup_InputSegments, &AAFTypeID_SegmentStrongReferenceVector);

		__td.ll[__td.lv] = InputSegments->Header->_entryCount;

		aafi->ctx.current_clip_is_combined            = 1;
		aafi->ctx.current_combined_clip_total_channel = InputSegments->Header->_entryCount;
		aafi->ctx.current_combined_clip_channel_num   = 0;

		aaf_foreach_ObjectInSet (&InputSegment, InputSegments, NULL)
		{
			aafi_parse_Segment (aafi, InputSegment, &__td);

			aafi->ctx.current_combined_clip_channel_num++;
			__td.ll[__td.lv]--;
		}

		/*
		 * Sets the track format.
		 */

		aafiAudioTrack* current_track = (aafiAudioTrack*)aafi->ctx.current_track;

		aafiTrackFormat_e track_format = AAFI_TRACK_FORMAT_UNKNOWN;

		if (aafi->ctx.current_combined_clip_total_channel == 2) {
			track_format = AAFI_TRACK_FORMAT_STEREO;
		} else if (aafi->ctx.current_combined_clip_total_channel == 6) {
			track_format = AAFI_TRACK_FORMAT_5_1;
		} else if (aafi->ctx.current_combined_clip_total_channel == 8) {
			track_format = AAFI_TRACK_FORMAT_7_1;
		} else {
			DUMP_OBJ_ERROR (aafi, OpGroup, &__td, "Unknown track format (%u)", aafi->ctx.current_combined_clip_total_channel);

			/*
			 * Reset multichannel track context.
			 */

			aafi->ctx.current_clip_is_combined            = 0;
			aafi->ctx.current_combined_clip_total_channel = 0;
			aafi->ctx.current_combined_clip_channel_num   = 0;

			return -1;
		}

		if (current_track->format != AAFI_TRACK_FORMAT_NOT_SET &&
		    current_track->format != track_format) {
			DUMP_OBJ_ERROR (aafi, OpGroup, &__td, "Track format (%u) does not match current clip (%u)", current_track->format, track_format);

			/*
			 * Reset multichannel track context.
			 */

			aafi->ctx.current_clip_is_combined            = 0;
			aafi->ctx.current_combined_clip_total_channel = 0;
			aafi->ctx.current_combined_clip_channel_num   = 0;

			return -1;
		}

		current_track->format = track_format;

		/*
		 * Reset multichannel track context.
		 */

		aafi->ctx.current_clip_is_combined            = 0;
		aafi->ctx.current_combined_clip_total_channel = 0;
		aafi->ctx.current_combined_clip_channel_num   = 0;

		// return;
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioGain)) {
		aafObject* Param      = NULL;
		aafObject* Parameters = aaf_get_propertyValue (OpGroup, PID_OperationGroup_Parameters, &AAFTypeID_ParameterStrongReferenceVector);

		if (Parameters == NULL) {
			DUMP_OBJ_ERROR (aafi, OpGroup, &__td, "Missing PID_OperationGroup_Parameters");
			rc = -1;
			goto end; // we still have to parse segments
		}

		/* Retrieve AAFParameterDef_Amplitude parameter */

		aaf_foreach_ObjectInSet (&Param, Parameters, NULL)
		{
			aafUID_t* ParamDef = aaf_get_propertyValue (Param, PID_Parameter_Definition, &AAFTypeID_AUID);

			if (aafUIDCmp (ParamDef, &AAFParameterDef_Amplitude))
				break;
		}

		if (Param == NULL) {
			DUMP_OBJ_ERROR (aafi, OpGroup, &__td, "Missing Parameter ParameterDef_Amplitude");
			rc = -1;
			goto end; // we still have to parse segments
		}

		DUMP_OBJ (aafi, OpGroup, &__td);

		__td.ll[__td.lv] = 2;

		rc = parse_Parameter (aafi, Param, &__td);

		// if ( rc == 0 ) {
		// 	DUMP_OBJ( aafi, OpGroup, &__td );
		// }
		// else {
		// 	DUMP_OBJ_ERROR( aafi, OpGroup, &__td, "Failed parsing parameter" );
		// }

	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_StereoAudioGain)) {
		DUMP_OBJ_NO_SUPPORT (aafi, OpGroup, &__td);
		/* TODO Unknown usage and implementation */
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioPan)) {
		/* TODO Should Only be Track-based (first Segment of TimelineMobSlot.) */

		/*
		 * We have to loop because of custom Parameters.
		 * Seen in AVID Media Composer AAFs (test.aaf). TODO ParamDef PanVol_IsTrimGainEffect ?
		 */

		aafObject* Param      = NULL;
		aafObject* Parameters = aaf_get_propertyValue (OpGroup, PID_OperationGroup_Parameters, &AAFTypeID_ParameterStrongReferenceVector);

		if (Parameters == NULL) {
			DUMP_OBJ_ERROR (aafi, OpGroup, &__td, "Missing PID_OperationGroup_Parameters");
			rc = -1;
			goto end; // we still have to parse segments
		}

		/* Retrieve AAFParameterDef_Pan parameter */

		aaf_foreach_ObjectInSet (&Param, Parameters, NULL)
		{
			aafUID_t* ParamDef = aaf_get_propertyValue (Param, PID_Parameter_Definition, &AAFTypeID_AUID);

			if (aafUIDCmp (ParamDef, &AAFParameterDef_Pan))
				break;
		}

		if (Param == NULL) {
			DUMP_OBJ_ERROR (aafi, OpGroup, &__td, "Missing Parameter ParameterDef_Amplitude");
			rc = -1;
			goto end; // we still have to parse segments
		}

		DUMP_OBJ (aafi, OpGroup, &__td);

		__td.ll[__td.lv] = 2;

		rc = parse_Parameter (aafi, Param, &__td);

		// if ( rc == 0 ) {
		// 	DUMP_OBJ( aafi, OpGroup, &__td );
		// }
		// else {
		// 	DUMP_OBJ_ERROR( aafi, OpGroup, &__td, "Failed parsing parameter" );
		// }
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioMixdown)) {
		DUMP_OBJ_NO_SUPPORT (aafi, OpGroup, &__td);
		/* TODO Unknown usage and implementation */
	} else {
		DUMP_OBJ_NO_SUPPORT (aafi, OpGroup, &__td);
	}

end:

	/*
	 * Parses Segments in the OperationGroup::InputSegments, only if
	 * OperationGroup is not a Transition as a Transition has no InputSegments,
	 * and not an AudioChannelCombiner as they were already parsed.
	 */

	if (aafUIDCmp (OpGroup->Parent->Class->ID, &AAFClassID_Transition) == 0 &&
	    aafUIDCmp (OperationIdentification, &AAFOperationDef_AudioChannelCombiner) == 0) {
		aafObject* InputSegment  = NULL;
		aafObject* InputSegments = aaf_get_propertyValue (OpGroup, PID_OperationGroup_InputSegments, &AAFTypeID_SegmentStrongReferenceVector);

		int i            = 0;
		__td.ll[__td.lv] = (InputSegments) ? InputSegments->Header->_entryCount : 0;

		aaf_foreach_ObjectInSet (&InputSegment, InputSegments, NULL)
		{
			__td.ll[__td.lv] = __td.ll[__td.lv] - i++;
			aafi_parse_Segment (aafi, InputSegment, &__td);
		}
	}

	/* End of current OperationGroup context. */

	aafObject* Obj = OpGroup;
	for (; Obj != NULL && aafUIDCmp (Obj->Class->ID, &AAFClassID_ContentStorage) == 0; Obj = Obj->Parent)
		if (!aafUIDCmp (Obj->Class->ID, &AAFClassID_OperationGroup))
			break;

	if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioGain)) {
		if (!aafUIDCmp (Obj->Class->ID, &AAFClassID_TimelineMobSlot)) {
			if (aafi->ctx.clips_using_gain == 0) {
				aafi_freeAudioGain (aafi->ctx.current_clip_gain);
			}

			if (aafi->ctx.clips_using_automation == 0) {
				aafi_freeAudioGain (aafi->ctx.current_clip_automation);
			}

			/* Clip-based Gain */
			aafi->ctx.current_clip_is_muted   = 0;
			aafi->ctx.current_clip_gain       = NULL;
			aafi->ctx.current_clip_automation = NULL;
			aafi->ctx.clips_using_gain        = 0;
			aafi->ctx.clips_using_automation  = 0;
		}

		// free( aafi->ctx.current_track->gain );
		// aafi->ctx.current_track->gain = NULL;
	}

	return rc;
}

static int
parse_SourceClip (AAF_Iface* aafi, aafObject* SourceClip, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	__td.hc = 1; // link to MasterMob, SourceMob

	aafUID_t* DataDefinition = get_Component_DataDefinition (aafi, SourceClip);

	if (DataDefinition == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve DataDefinition");
		return -1;
	}

	aafObject* ParentMob = get_Object_Ancestor (aafi, SourceClip, &AAFClassID_Mob);

	if (ParentMob == NULL) {
		DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve parent Mob");
		return -1;
	}

	aafMobID_t* parentMobID = aaf_get_propertyValue (ParentMob, PID_Mob_MobID, &AAFTypeID_MobIDType);

	if (parentMobID == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Missing parent Mob PID_Mob_MobID");
		return -1;
	}

	aafMobID_t* sourceID = aaf_get_propertyValue (SourceClip, PID_SourceReference_SourceID, &AAFTypeID_MobIDType);

	if (sourceID == NULL) { /* opt */
		                /* NOTE: PID_SourceReference_SourceID is optionnal, there might be none. */
		                // DUMP_OBJ_ERROR( aafi, SourceClip, &__td, "Missing PID_SourceReference_SourceID" );
		                // return -1;
	}

	uint32_t* SourceMobSlotID = aaf_get_propertyValue (SourceClip, PID_SourceReference_SourceMobSlotID, &AAFTypeID_UInt32);

	if (SourceMobSlotID == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Missing PID_SourceReference_SourceMobSlotID");
		return -1;
	}

	/*
	 * TODO: handle SourceReference::MonoSourceSlotIDs and associated conditional rules.
	 * (Multi-channels)
	 */

	aafObject* refMob     = NULL;
	aafObject* refMobSlot = NULL;

	if (sourceID == NULL) {
		/*
		 * p.49 : To create a SourceReference that refers to a MobSlot within
		 * the same Mob as the SourceReference, omit the SourceID property.
		 *
		 * [SourceID] Identifies the Mob being referenced. If the property has a
		 * value 0, it means that the Mob owning the SourceReference describes
		 * the original source.
		 *
		 * TODO: in that case, is MobSlots NULL ?
		 */

		// sourceID = parentMobID;
		// refMob = ParentMob;
	} else {
		refMob = aaf_get_MobByID (aafi->aafd->Mobs, sourceID);

		if (refMob == NULL) {
			DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve target Mob by ID : %ls", aaft_MobIDToText (sourceID));
			return -1;
		}

		aafObject* refMobSlots = aaf_get_propertyValue (refMob, PID_Mob_Slots, &AAFTypeID_MobSlotStrongReferenceVector);

		if (refMobSlots == NULL) {
			DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Missing target Mob PID_Mob_Slots");
			return -1;
		}

		refMobSlot = aaf_get_MobSlotBySlotID (refMobSlots, *SourceMobSlotID);

		if (refMobSlot == NULL) {
			/* TODO check if there is a workaround :
			 * AAFInfo --aaf-clips '/home/agfline/Developpement/libaaf_testfiles/ADP/ADP_STTRACK_CLIPGAIN_TRACKGAIN_XFADE_NOOPTONEXPORT.aaf'
			 */
			DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve target MobSlot ID : %u", *SourceMobSlotID);
			return -1;
		}
	}

	/* *** Clip *** */

	if (aafUIDCmp (ParentMob->Class->ID, &AAFClassID_CompositionMob)) {
		// DUMP_OBJ( aafi, SourceClip, &__td );

		int64_t* length = aaf_get_propertyValue (SourceClip, PID_Component_Length, &AAFTypeID_LengthType);

		if (length == NULL) {
			DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Missing PID_Component_Length");
			return -1;
		}

		int64_t* startTime = aaf_get_propertyValue (SourceClip, PID_SourceClip_StartTime, &AAFTypeID_PositionType);

		if (startTime == NULL) {
			DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Missing PID_SourceClip_StartTime");
			return -1;
		}

		struct aafiContext ctxBackup;

		aafUID_t* CurrentUsageCode = aaf_get_propertyValue (ParentMob, PID_Mob_UsageCode, &AAFTypeID_UsageType);

		if (CurrentUsageCode == NULL) {
			/* NOTE: PID_Mob_UsageCode is optionnal, there might be none. */
			// DUMP_OBJ_ERROR( aafi, SourceClip, &__td, "Missing PID_Mob_UsageCode" );
			// return -1;
		}

		// if ( aafUIDCmp( aafi->aafd->Header.OperationalPattern, &AAFOPDef_EditProtocol ) )
		{
			// if ( (CurrentUsageCode && aafUIDCmp( CurrentUsageCode, &AAFUsage_SubClip )) || CurrentUsageCode == NULL ) {
			// 	aafi_trace_obj( aafi, SourceClip, ANSI_COLOR_YELLOW );
			// }
			// else {
			// 	aafi_trace_obj( aafi, SourceClip, ANSI_COLOR_MAGENTA );
			// }

			/*
			 * If SourceClip points to a CompositionMob instead of a MasterMob, we
			 * are at the begining (or inside) a derivation chain.
			 */

			if (aafUIDCmp (refMob->Class->ID, &AAFClassID_CompositionMob)) {
				// debug( "REF TO SUBCLIP" );
				//
				// debug( "SourceClip::SourceID        : %ls", aaft_MobIDToText( sourceID ) );
				// debug( "CurrentMob::MobID           : %ls", aaft_MobIDToText( parentMobID ) );
				// debug( "SourceClip::SourceMobSlotID : %i", *SourceMobSlotID );
				// debug( "UsageCode                   : %ls", aaft_UsageCodeToText( UsageCode ) );

				if (refMobSlot == NULL) {
					/* TODO isn't it already checked above ? */
					DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Missing target MobSlot");
					return -1;
				}

				DUMP_OBJ (aafi, SourceClip, &__td);

				/* Only to print trace */
				__td.lv++;
				DUMP_OBJ (aafi, refMob, &__td);
				// __td.lv++;

				memcpy (&ctxBackup, &(aafi->ctx), sizeof (struct aafiContext));

				RESET_CONTEXT (aafi->ctx);

				aafi->ctx.current_track              = ctxBackup.current_track;
				aafi->ctx.is_inside_derivation_chain = 1;

				parse_MobSlot (aafi, refMobSlot, &__td);

				void* new_clip = aafi->ctx.current_clip;

				memcpy (&(aafi->ctx), &ctxBackup, sizeof (struct aafiContext));

				if (aafUIDCmp (DataDefinition, &AAFDataDef_Sound) ||
				    aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
					aafi->ctx.current_clip = (aafiAudioClip*)new_clip;

					if (new_clip && aafUIDCmp (CurrentUsageCode, &AAFUsage_TopLevel)) {
						/*
						 * All derivation chain calls ended.
						 *
						 * We came back at level zero of parse_SourceClip() nested calls, so
						 * the clip and its source was added, we only have to set its length,
						 * offset and gain with correct values.
						 *
						 * TODO: aafi->current_clip pointer to new_clip instead ?
						 */

						((aafiAudioClip*)new_clip)->len            = *length;
						((aafiAudioClip*)new_clip)->essence_offset = *startTime;
						((aafiAudioClip*)new_clip)->gain           = aafi->ctx.current_clip_gain;
						((aafiAudioClip*)new_clip)->automation     = aafi->ctx.current_clip_automation;
						((aafiAudioClip*)new_clip)->mute           = aafi->ctx.current_clip_is_muted;
						aafi->ctx.clips_using_gain++;
						aafi->ctx.clips_using_automation++;

						aafi->ctx.current_track->current_pos += ((aafiAudioClip*)new_clip)->len;
					}
				} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Picture) ||
				           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyPicture)) {
					if (new_clip && aafUIDCmp (CurrentUsageCode, &AAFUsage_TopLevel)) {
						/*
						 * All derivation chain calls ended.
						 *
						 * We came back at level zero of parse_SourceClip() nested calls, so
						 * the clip and its source was added, we only have to set its length,
						 * offset and gain with correct values.
						 */

						((aafiVideoClip*)new_clip)->len            = *length;
						((aafiVideoClip*)new_clip)->essence_offset = *startTime;

						aafi->Video->Tracks->current_pos += ((aafiVideoClip*)new_clip)->len;
					}
				}

				return 0;

			} else if (aafUIDCmp (refMob->Class->ID, &AAFClassID_MasterMob)) {
				/*
				 * We are inside the derivation chain and we reached the SourceClip
				 * pointing to MasterMob (the audio essence).
				 *
				 * Thus, we can add the clip and parse the audio essence normaly.
				 */
			}
		}

		if (aafUIDCmp (DataDefinition, &AAFDataDef_Sound) ||
		    aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
			// if ( *length == 1 ) {
			// 	/*
			// 	 * If length equals 1 EditUnit, the clip is probably a padding for "Media Composer Compatibility".
			// 	 * Therefore, we don't need it.
			// 	 *
			// 	 * TODO BUT this could also be some rendered fade.. we should find a way to distinguish between the two.
			// 	 */
			//
			// 	// aaf_dump_ObjectProperties( aafi->aafd, SourceClip );
			//
			// 	warning( "Got a 1 EU length clip, probably some NLE compatibility padding : Skipping." );
			//
			//
			//
			// 	if ( aafi->ctx.current_track_is_multichannel == 0 ) {
			// 		aafi->ctx.current_pos += *length;
			// 	}
			// 	else {
			// 		aafi->ctx.current_multichannel_track_clip_length = *length;
			// 	}
			//
			// 	return -1;
			// }

			if (aafi->ctx.current_clip_is_combined &&
			    aafi->ctx.current_combined_clip_channel_num > 0) {
				/*
				 * Parsing multichannel audio clip (AAFOperationDef_AudioChannelCombiner)
				 * We already parsed first SourceClip in AAFOperationDef_AudioChannelCombiner.
				 * We just have to check everything match for all clips left (each clip represents a channel)
				 */

				if (aafi->ctx.current_clip->len != *length) {
					DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "SourceClip length does not match first one in AAFOperationDef_AudioChannelCombiner");
					return -1;
				}

				if (!aafMobIDCmp (aafi->ctx.current_clip->masterMobID, sourceID)) {
					DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "SourceClip SourceID does not match first one in AAFOperationDef_AudioChannelCombiner");
					return -1;
				}

				DUMP_OBJ (aafi, SourceClip, &__td);
				return 0;
			}

			/*
			 * Create new clip, only if we are parsing a single mono clip, or if
			 * we are parsing the first SourceClip describing a multichannel clip
			 * inside an AAFOperationDef_AudioChannelCombiner
			 */

			aafiTimelineItem* item = aafi_newTimelineItem (aafi, aafi->ctx.current_track, AAFI_AUDIO_CLIP);

			aafiAudioClip* audioClip = item->data; //(aafiAudioClip*)&item->data;

			aafi->ctx.clips_using_gain++;
			aafi->ctx.clips_using_automation++;
			audioClip->gain       = aafi->ctx.current_clip_gain;
			audioClip->automation = aafi->ctx.current_clip_automation;
			audioClip->mute       = aafi->ctx.current_clip_is_muted;
			audioClip->pos        = aafi->ctx.current_track->current_pos;
			audioClip->len        = *length;

			audioClip->essence_offset = *startTime;

			aafi->ctx.current_clip = audioClip;

			/*
			 * p.49 : To create a SourceReference that refers to a MobSlot within
			 * the same Mob as the SourceReference, omit the SourceID property.
			 *
			 * NOTE: This should not happen here because The "CompositionMob > SourceClip::SourceID"
			 * should always point to the corresponding "MasterMob", that is a different Mob.
			 */

			// if ( aafMobIDCmp( aafi->ctx.current_clip->masterMobID, sourceID ) ) {
			// 	debug( "SAME_SOURCE_ID : %ls", AUIDToText(sourceID) );
			// } else {
			// 	debug( "DIFFERENT_SOURCE_ID : %ls", AUIDToText(sourceID) );
			// }

			audioClip->masterMobID = sourceID;

			// if ( audioClip->masterMobID == NULL ) {
			// 	audioClip->masterMobID = aaf_get_propertyValue( ParentMob, PID_Mob_MobID, &AAFTypeID_MobIDType );
			// 	warning( "Missing SourceReference::SourceID, retrieving from parent Mob." );
			// }

			if (!aafi->ctx.is_inside_derivation_chain) {
				/*
			 * We DO NOT update current pos when SourceClip belongs to a sub CompositionMob
			 * because in that case, current pos was already updated by initial SourceClip
			 * pointing to AAFClassID_CompositionMob

04606│├──◻ AAFClassID_TimelineMobSlot [slot:16 track:8] (DataDef : AAFDataDef_LegacySound)
02064││    └──◻ AAFClassID_Sequence
02037││         ├──◻ AAFClassID_Filler
     ││         │
02502││         ├──◻ AAFClassID_OperationGroup (OpIdent: AAFOperationDef_MonoAudioGain)   (MetaProps: ComponentAttributeList[0xffcc])
03780││         │    ├──◻ AAFClassID_ConstantValue
POS UPDATED HERE --> └──◻ AAFClassID_SourceClip
02842││         │         └──◻ AAFClassID_CompositionMob (UsageCode: AAFUsage_AdjustedClip) : Islamic Call to Prayer - Amazing Adhan by Edris Aslami.mp3.new.01  (MetaProps: MobAttributeList[0xfff9] ConvertFrameRate[0xfff8])
04606││         │              └──◻ AAFClassID_TimelineMobSlot [slot:2 track:2] (DataDef : AAFDataDef_LegacySound)
02502││         │                   └──◻ AAFClassID_OperationGroup (OpIdent: AAFOperationDef_MonoAudioGain)
03780││         │                        ├──◻ AAFClassID_ConstantValue
POS NOT UPDATED HERE ------------------> └──◻ AAFClassID_SourceClip
03085││         │                             └──◻ AAFClassID_MasterMob (UsageCode: n/a) : Islamic Call to Prayer - Amazing Adhan by Edris Aslami.mp3.new.01  (MetaProps: AppCode[0xfffa])
04705││         │                                  └──◻ AAFClassID_TimelineMobSlot
03305││         │                                       └──◻ AAFClassID_SourceClip
04412││         │                                            ├──◻ AAFClassID_SourceMob (UsageCode: n/a) : Islamic Call to Prayer - Amazing Adhan by Edris Aslami.mp3  (MetaProps: MobAttributeList[0xfff9])
01400││         │                                            │    └──◻ AAFClassID_WAVEDescriptor
01555││         │                                            │         └──◻ AAFClassID_NetworkLocator : file:///MEDIA2/2199_Rapport_Astellas Main Content/audio/AX TEST.aaf
     ││         │                                            │
01800││         ├──◻ AAFClassID_Transition
02283││         │    └──◻ AAFClassID_OperationGroup (OpIdent: AAFOperationDef_MonoAudioDissolve)   (MetaProps: ComponentAttributeList[0xffcc])
03934││         │         └──◻ AAFClassID_VaryingValue
     ││         │
02502││         ├──◻ AAFClassID_OperationGroup (OpIdent: AAFOperationDef_MonoAudioGain)   (MetaProps: ComponentAttributeList[0xffcc])
03780││         │    ├──◻ AAFClassID_ConstantValue
02836││         │    └──◻ AAFClassID_SourceClip
02842││         │         └──◻ AAFClassID_CompositionMob (UsageCode: AAFUsage_AdjustedClip) : Islamic Call to Prayer - Amazing Adhan by Edris Aslami.mp3.new.01  (MetaProps: MobAttributeList[0xfff9] ConvertFrameRate[0xfff8])
04606││         │              └──◻ AAFClassID_TimelineMobSlot [slot:2 track:2] (DataDef : AAFDataDef_LegacySound)
02502││         │                   └──◻ AAFClassID_OperationGroup (OpIdent: AAFOperationDef_MonoAudioGain)
03780││         │                        ├──◻ AAFClassID_ConstantValue
03080││         │                        └──◻ AAFClassID_SourceClip
03085││         │                             └──◻ AAFClassID_MasterMob (UsageCode: n/a) : Islamic Call to Prayer - Amazing Adhan by Edris Aslami.mp3.new.01  (MetaProps: AppCode[0xfffa])
04705││         │                                  └──◻ AAFClassID_TimelineMobSlot
03270││         │                                       └──◻ AAFClassID_SourceClip Essence already parsed: Linking with Islamic Call to Prayer - Amazing Adhan by Edris Aslami.mp3.new.01
     ││         │
02037││         └──◻ AAFClassID_Filler
			 */

				aafi->ctx.current_track->current_pos += audioClip->len;
			}

			if (aafi->ctx.current_clip_is_combined == 0) {
				if (aafi->ctx.current_track->format != AAFI_TRACK_FORMAT_NOT_SET &&
				    aafi->ctx.current_track->format != AAFI_TRACK_FORMAT_MONO) {
					DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Track format (%u) does not match current clip (%u)", aafi->ctx.current_track->format, AAFI_TRACK_FORMAT_MONO);
				} else {
					aafi->ctx.current_track->format = AAFI_TRACK_FORMAT_MONO;
				}
			}

			if (aafUIDCmp (refMob->Class->ID, &AAFClassID_MasterMob)) {
				if (refMobSlot == NULL) {
					/* TODO isn't it already checked above ? */
					DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Missing target MobSlot");
					return -1;
				}

				DUMP_OBJ (aafi, SourceClip, &__td);

				/* Only to print trace */
				__td.lv++;
				DUMP_OBJ (aafi, refMob, &__td);

				memcpy (&ctxBackup, &(aafi->ctx), sizeof (struct aafiContext));

				RESET_CONTEXT (aafi->ctx);

				aafi->ctx.current_track = ctxBackup.current_track;
				aafi->ctx.current_clip  = audioClip;

				parse_MobSlot (aafi, refMobSlot, &__td);

				memcpy (&(aafi->ctx), &ctxBackup, sizeof (struct aafiContext));

			} else {
				DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "RefMob isn't MasterMob : %ls", aaft_ClassIDToText (aafi->aafd, refMob->Class->ID));
				// parse_CompositionMob( )
				return -1;
			}

		} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Picture) ||
		           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyPicture)) {
			if (aafi->Video->Tracks->Items) {
				DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Current implementation supports only one video clip");
				return -1;
			}

			/* Add the new clip */

			aafiTimelineItem* item = aafi_newTimelineItem (aafi, aafi->Video->Tracks, AAFI_VIDEO_CLIP);

			aafiVideoClip* videoClip = item->data; //(aafiVideoClip*)&item->data;

			videoClip->pos = aafi->Video->Tracks->current_pos;
			videoClip->len = *length;

			videoClip->essence_offset = *startTime;

			/*
			 * p.49 : To create a SourceReference that refers to a MobSlot within
			 * the same Mob as the SourceReference, omit the SourceID property.
			 *
			 * NOTE: This should not happen here because The "CompositionMob > SourceClip::SourceID"
			 * should always point to the corresponding "MasterMob", that is a different Mob.
			 */

			videoClip->masterMobID = sourceID;

			// if ( videoClip->masterMobID == NULL ) {
			// 	videoClip->masterMobID = aaf_get_propertyValue( ParentMob, PID_Mob_MobID, &AAFTypeID_MobIDType );
			// 	warning( "Missing SourceReference::SourceID, retrieving from parent Mob." );
			// }

			if (!aafUIDCmp (aafi->aafd->Header.OperationalPattern, &AAFOPDef_EditProtocol) ||
			    aafUIDCmp (CurrentUsageCode, &AAFUsage_TopLevel)) {
				/*
				 * NOTE for AAFOPDef_EditProtocol only :
				 *
				 * If SourceClip belongs to a TopLevel Mob, we can update position.
				 * Otherwise, it means we are inside a derivation chain ( ie: TopLevelCompositionMob -> SourceClip -> SubLevel:CompositionMob -> SourceClip )
				 * and the clip length is not the good one. In that case, position is updated above.
				 */

				aafi->Video->Tracks->current_pos += videoClip->len;
			}

			aafi->ctx.current_video_clip = videoClip;

			if (aafUIDCmp (refMob->Class->ID, &AAFClassID_MasterMob)) {
				if (refMobSlot == NULL) {
					/* TODO isn't it already checked above ? */
					DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Missing target MobSlot");
					return -1;
				}

				DUMP_OBJ (aafi, SourceClip, &__td);

				/* Only to print trace */
				__td.lv++;
				DUMP_OBJ (aafi, refMob, &__td);

				// memcpy( &ctxBackup, &(aafi->ctx), sizeof(struct aafiContext) );
				//
				// RESET_CONTEXT( aafi->ctx );

				parse_MobSlot (aafi, refMobSlot, &__td);

				// memcpy( &(aafi->ctx), &ctxBackup, sizeof(struct aafiContext) );
			}
		}
	}

	/* *** Essence *** */

	else if (aafUIDCmp (ParentMob->Class->ID, &AAFClassID_MasterMob)) {
		aafMobID_t* masterMobID = aaf_get_propertyValue (ParentMob, PID_Mob_MobID, &AAFTypeID_MobIDType);

		if (masterMobID == NULL) {
			DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve parent Mob PID_Mob_MobID");
			return -1;
		}

		aafObject* ParentMobSlot = get_Object_Ancestor (aafi, SourceClip, &AAFClassID_MobSlot);

		if (ParentMobSlot == NULL) {
			DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve parent MobSlot");
			return -1;
		}

		uint32_t* masterMobSlotID = aaf_get_propertyValue (ParentMobSlot, PID_MobSlot_SlotID, &AAFTypeID_UInt32);

		// uint32_t *essenceChannelNum = aaf_get_propertyValue( ParentMobSlot, PID_MobSlot_PhysicalTrackNumber, &AAFTypeID_UInt32 );
		//
		// if ( essenceChannelNum == NULL ) { /* opt */
		// 	debug( "PhysicalTrackNumber: NOT SET" );
		// } else {
		// 	debug( "PhysicalTrackNumber: %u", *essenceChannelNum );
		// }

		if (aafUIDCmp (DataDefinition, &AAFDataDef_Sound) ||
		    aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
			/* Check if this Essence has already been retrieved */

			// int slotID = MobSlot->Entry->_localKey;

			// aafObject *Obj = aaf_get_MobByID( aafi->aafd, SourceID );
			// debug( "SourceMobID : %ls", aaft_MobIDToText(SourceID) );
			// debug( "MasterMobID : %ls", aaft_MobIDToText(mobID) );

			if (!aafi->ctx.current_clip) {
				DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "aafi->ctx.current_clip not set");
				return -1;
			}

			aafiAudioEssence* audioEssence = NULL;

			foreachEssence (audioEssence, aafi->Audio->Essences)
			{
				if (aafMobIDCmp (audioEssence->sourceMobID, sourceID) && audioEssence->sourceMobSlotID == (unsigned)*SourceMobSlotID) {
					/* Essence already retrieved */
					aafi->ctx.current_clip->Essence = audioEssence;
					__td.eob                        = 1;
					DUMP_OBJ_INFO (aafi, SourceClip, &__td, "Essence already parsed: Linking with %ls", audioEssence->file_name);
					return 0;
				}
			}

			/* new Essence, carry on. */

			audioEssence = aafi_newAudioEssence (aafi);

			aafi->ctx.current_essence = audioEssence;

			audioEssence->masterMobSlotID = *masterMobSlotID;
			audioEssence->masterMobID     = masterMobID;

			audioEssence->file_name = aaf_get_propertyValue (ParentMob, PID_Mob_Name, &AAFTypeID_String);

			if (audioEssence->file_name == NULL) {
				debug ("Missing MasterMob::PID_Mob_Name (essence file name)");
			}

			/*
			 * p.49 : To create a SourceReference that refers to a MobSlot within
			 * the same Mob as the SourceReference, omit the SourceID property.
			 */

			audioEssence->sourceMobSlotID = *SourceMobSlotID;
			audioEssence->sourceMobID     = sourceID;

			// if ( audioEssence->sourceMobID == NULL ) {
			// 	audioEssence->sourceMobID = aaf_get_propertyValue( ParentMob, PID_Mob_MobID, &AAFTypeID_MobIDType );
			// 	warning( "Could not retrieve SourceReference::SourceID, retrieving from parent Mob." );
			// }

			DUMP_OBJ (aafi, SourceClip, &__td);

			aafObject* SourceMob = aaf_get_MobByID (aafi->aafd->Mobs, audioEssence->sourceMobID);

			if (SourceMob == NULL) {
				DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve SourceMob by ID : %ls", aaft_MobIDToText (audioEssence->sourceMobID));
				return -1;
			}

			audioEssence->SourceMob = SourceMob;

			aafObject* EssenceData = get_EssenceData_By_MobID (aafi, audioEssence->sourceMobID);

			if (EssenceData)
				__td.ll[__td.lv] = 2;

			parse_SourceMob (aafi, SourceMob, &__td);

			__td.ll[__td.lv] = 0;

			if (EssenceData == NULL) {
				/*
				 * It means essence is not embedded.
				 */
				// return -1;
			} else {
				parse_EssenceData (aafi, EssenceData, &__td);
			}

			audioEssence->unique_file_name = build_unique_audiofilename (aafi, audioEssence);

			aafi->ctx.current_clip->Essence = audioEssence;

			// aafi_trace_obj( aafi, SourceClip, ANSI_COLOR_MAGENTA );

		} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Picture) ||
		           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyPicture)) {
			/* Check if this Essence has already been retrieved */

			// int slotID = MobSlot->Entry->_localKey;

			// aafObject *Obj = aaf_get_MobByID( aafi->aafd, sourceID );
			// debug( "SourceMobID : %ls", aaft_MobIDToText(sourceID) );
			// debug( "MasterMobID : %ls", aaft_MobIDToText(mobID) );

			if (!aafi->ctx.current_video_clip) {
				DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "aafi->ctx.current_video_clip not set");
				return -1;
			}

			aafiVideoEssence* videoEssence = NULL;

			foreachEssence (videoEssence, aafi->Video->Essences)
			{
				if (aafMobIDCmp (videoEssence->sourceMobID, sourceID) && videoEssence->sourceMobSlotID == (unsigned)*SourceMobSlotID) {
					/* Essence already retrieved */
					aafi->ctx.current_video_clip->Essence = videoEssence;
					return 0;
				}
			}

			/* new Essence, carry on. */

			videoEssence = aafi_newVideoEssence (aafi);

			aafi->ctx.current_video_clip->Essence = videoEssence;

			videoEssence->masterMobSlotID = *masterMobSlotID;
			videoEssence->masterMobID     = masterMobID;

			videoEssence->file_name = aaf_get_propertyValue (ParentMob, PID_Mob_Name, &AAFTypeID_String);

			/*
			 * p.49 : To create a SourceReference that refers to a MobSlot within
			 * the same Mob as the SourceReference, omit the SourceID property.
			 */

			videoEssence->sourceMobSlotID = *SourceMobSlotID;
			videoEssence->sourceMobID     = sourceID;

			// if ( audioEssence->sourceMobID == NULL ) {
			// 	audioEssence->sourceMobID = aaf_get_propertyValue( ParentMob, PID_Mob_MobID, &AAFTypeID_MobIDType );
			// 	warning( "Could not retrieve SourceReference::SourceID, retrieving from parent Mob." );
			// }

			aafi->ctx.current_video_essence = videoEssence;

			DUMP_OBJ (aafi, SourceClip, &__td);

			aafObject* SourceMob = aaf_get_MobByID (aafi->aafd->Mobs, videoEssence->sourceMobID);

			if (SourceMob == NULL) {
				DUMP_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve SourceMob by ID : %ls", aaft_MobIDToText (videoEssence->sourceMobID));
				return -1;
			}

			videoEssence->SourceMob = SourceMob;

			parse_SourceMob (aafi, SourceMob, &__td);

			aafObject* EssenceData = get_EssenceData_By_MobID (aafi, videoEssence->sourceMobID);

			if (EssenceData == NULL) {
				/*
				 * It means essence is not embedded.
				 */

				// return -1;
			} else {
				parse_EssenceData (aafi, EssenceData, &__td);
			}

			videoEssence->unique_file_name = build_unique_videofilename (aafi, videoEssence);

			// aafi_trace_obj( aafi, SourceClip, ANSI_COLOR_MAGENTA );

			// debug( "Master MOB SOURCE CLIP" );
			//
			// aafiVideoEssence *videoEssence = aafi_newVideoEssence( aafi );
			//
			// aafi->ctx.current_essence = (aafiVideoEssence*)videoEssence;
			//
			//
			// videoEssence->file_name = aaf_get_propertyValue( /*aafi->ctx.*/ParentMob, PID_Mob_Name, &AAFTypeID_String );
			//
			//
			// videoEssence->masterMobID = aaf_get_propertyValue( /*aafi->ctx.*/ParentMob, PID_Mob_MobID, &AAFTypeID_MobIDType );
			//
			// if ( videoEssence->masterMobID == NULL ) {
			// 	aafi_trace_obj( aafi, SourceClip, ANSI_COLOR_RED );
			// 	error( "Could not retrieve Mob::MobID." );
			// 	return -1;
			// }
			//
			// /*
			//  * p.49 : To create a SourceReference that refers to a MobSlot within
			//  * the same Mob as the SourceReference, omit the SourceID property.
			//  */
			//
			// videoEssence->sourceMobID = SourceID; //aaf_get_propertyValue( SourceClip, PID_SourceReference_SourceID, &AAFTypeID_MobIDType );
			//
			// if ( videoEssence->sourceMobID == NULL ) {
			// 	// aafObject *Mob = NULL;
			// 	//
			// 	// for ( Mob = SourceClip; Mob != NULL; Mob = Mob->Parent ) {
			// 	// 	if ( aafUIDCmp( Mob->Class->ID, &AAFClassID_MasterMob ) )
			// 	// 		break;
			// 	// }
			//
			// 	videoEssence->sourceMobID = aaf_get_propertyValue( ParentMob, PID_Mob_MobID, &AAFTypeID_MobIDType );
			//
			// 	warning( "Could not retrieve SourceReference::SourceID, retrieving from parent Mob." );
			// }
			//
			//
			//
			// aafObject *SourceMob = aaf_get_MobByID( aafi->aafd->Mobs, videoEssence->sourceMobID );
			//
			// if ( SourceMob == NULL ) {
			// 	aafi_trace_obj( aafi, SourceClip, ANSI_COLOR_RED );
			// 	error( "Could not retrieve SourceMob." );
			// 	return -1;
			// }
			//
			// videoEssence->SourceMob = SourceMob;
			//
			//
			//
			// // parse_SourceMob( aafi, SourceMob );
			//
			//
			// /* TODO the following must be moved to parse_SourceMob() !!! */
			//
			// aafObject *EssenceDesc = aaf_get_propertyValue( SourceMob, PID_SourceMob_EssenceDescription, &AAFTypeID_EssenceDescriptorStrongReference );
			//
			// if ( EssenceDesc == NULL ) {
			// 	aafi_trace_obj( aafi, SourceClip, ANSI_COLOR_RED );
			// 	error( "Could not retrieve EssenceDesc." );
			// 	return -1;
			// }
			//
			//
			// // TODO
			// parse_EssenceDescriptor( aafi, EssenceDesc );
			//
			//
			//
			// videoEssence->unique_file_name = build_unique_videofilename( aafi, videoEssence );
			//
			//
			//
			// /* NOTE since multiple clips can point to the same MasterMob, we have to loop. */
			//
			// aafiVideoTrack   * videoTrack = NULL;
			// aafiTimelineItem * videoItem  = NULL;
			//
			// foreach_videoTrack( videoTrack, aafi ) {
			// 	foreach_Item( videoItem, videoTrack ) {
			// 		if ( videoItem->type != AAFI_VIDEO_CLIP ) {
			// 			continue;
			// 		}
			//
			// 		aafiVideoClip *videoClip = (aafiVideoClip*)&videoItem->data;
			//
			// 		if ( aafMobIDCmp( videoClip->masterMobID, videoEssence->masterMobID ) ) {
			// 			debug( "FOUND VIDEO ESSENCE CLIP !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" );
			// 			videoClip->Essence = videoEssence;
			// 		}
			// 	}
			// }
			//
			// aafi_trace_obj( aafi, SourceClip, ANSI_COLOR_MAGENTA );
			//
		}
	} else {
		DUMP_OBJ_NO_SUPPORT (aafi, SourceClip, &__td);
		return -1;
	}

	return 0;
}

static int
parse_Selector (AAF_Iface* aafi, aafObject* Selector, td* __ptd)
{
	/*
	 * The Selector class is a sub-class of the Segment class.
	 *
	 * The Selector class provides the value of a single Segment (PID_Selector_Selected)
	 * while preserving references to unused alternatives (PID_Selector_Alternates)
	 */

	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	if (resolve_AAF (aafi)) {
		return resolve_parse_aafObject_Selector (aafi, Selector, &__td);
	}

	aafObject* Selected = aaf_get_propertyValue (Selector, PID_Selector_Selected, &AAFTypeID_SegmentStrongReference);

	if (Selected == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, Selector, &__td, "Missing PID_Selector_Selected");
		return -1;
	}

	// aafObject *Alternate = NULL;
	aafObject* Alternates = aaf_get_propertyValue (Selector, PID_Selector_Alternates, &AAFTypeID_SegmentStrongReferenceVector);

	if (Alternates == NULL) { /* opt */
		                  // DUMP_OBJ_WARNING( aafi, Selector, &__td, "Missing PID_Selector_Alternates" );
	}

	DUMP_OBJ (aafi, Selector, &__td);

	/* without specific software implementation we stick to Selected and forget about Alternates */
	return aafi_parse_Segment (aafi, Selected, &__td);
}

/*
 *           Parameter (abs)
 *               |
 *       ,--------------.
 *       |              |
 * ConstantValue   VaryingValue
 *
 *
 * A Parameter object shall be owned by an OperationGroup object.
 */

static int
parse_Parameter (AAF_Iface* aafi, aafObject* Parameter, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	if (aafUIDCmp (Parameter->Class->ID, &AAFClassID_ConstantValue)) {
		return parse_ConstantValue (aafi, Parameter, &__td);
	} else if (aafUIDCmp (Parameter->Class->ID, &AAFClassID_VaryingValue)) {
		return parse_VaryingValue (aafi, Parameter, &__td);
	}

	return -1;
}

static int
parse_ConstantValue (AAF_Iface* aafi, aafObject* ConstantValue, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	// __td.sub = 1;

	if (!aaf_get_propertyValue (ConstantValue->Parent, PID_OperationGroup_InputSegments, &AAFTypeID_SegmentStrongReferenceVector)) {
		__td.eob = 1;
	}

	aafUID_t* ParamDef = aaf_get_propertyValue (ConstantValue, PID_Parameter_Definition, &AAFTypeID_AUID);

	if (ParamDef == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, ConstantValue, &__td, "Missing PID_Parameter_Definition");
		return -1;
	}

	aafUID_t* OperationIdentification = get_OperationGroup_OperationIdentification (aafi, ConstantValue->Parent);

	if (OperationIdentification == NULL) {
		DUMP_OBJ_ERROR (aafi, ConstantValue, &__td, "Could not retrieve OperationIdentification");
		return -1;
	}

	if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioGain) &&
	    aafUIDCmp (ParamDef, &AAFParameterDef_Amplitude)) {
		aafIndirect_t* Indirect = aaf_get_propertyValue (ConstantValue, PID_ConstantValue_Value, &AAFTypeID_Indirect);

		if (Indirect == NULL) {
			DUMP_OBJ_ERROR (aafi, ConstantValue, &__td, "Missing PID_ConstantValue_Value or wrong AAFTypeID");
			return -1;
		}

		aafRational_t* multiplier = aaf_get_indirectValue (aafi->aafd, Indirect, &AAFTypeID_Rational);

		if (multiplier == NULL) {
			DUMP_OBJ_ERROR (aafi, ConstantValue, &__td, "Could not retrieve Indirect value for PID_ConstantValue_Value");
			return -1;
		}

		aafiAudioGain* Gain = calloc (sizeof (aafiAudioGain), sizeof (unsigned char));

		Gain->pts_cnt = 1;
		Gain->value   = calloc (1, sizeof (aafRational_t*));
		Gain->flags |= AAFI_AUDIO_GAIN_CONSTANT;

		memcpy (&Gain->value[0], multiplier, sizeof (aafRational_t));

		/*
		 * Loop through ancestors to find out who is the parent of OperationGroup.
		 * If it belongs to TimelineMobSlot, that means the Parameter is attached
		 * to a Track. If it belongs to a Component, the Parameter is attached to
		 * a clip.
		 *
		 * NOTE: We can't just check the Parent since we can have nested OperationGroups
		 * providing different effects like Pan, Gain, CustomFx.. Therefore looping
		 * is required.
		 */

		aafObject* Obj = ConstantValue->Parent; // Start with the last OperationGroup
		for (; Obj != NULL && aafUIDCmp (Obj->Class->ID, &AAFClassID_ContentStorage) == 0; Obj = Obj->Parent)
			if (!aafUIDCmp (Obj->Class->ID, &AAFClassID_OperationGroup))
				break;

		if (aafUIDCmp (Obj->Class->ID, &AAFClassID_TimelineMobSlot)) {
			/* Track-based Gain */
			aafi->ctx.current_track->gain = Gain;
		} else {
			/* Clip-based Gain */
			if (aafi->ctx.current_clip_gain) {
				DUMP_OBJ_ERROR (aafi, ConstantValue, &__td, "Clip gain was already set : +%05.1lf dB", 20 * log10 (aafRationalToFloat (aafi->ctx.current_clip_gain->value[0])));

				// for ( int i = 0; i < aafi->ctx.current_clip_gain->pts_cnt; i++ ) {
				// 	debug( "   VaryingValue:  _time: %f   _value: %f",
				// 		aafRationalToFloat( aafi->ctx.current_clip_gain->time[i]  ),
				// 		aafRationalToFloat( aafi->ctx.current_clip_gain->value[i] ) );
				// }

				aafi_freeAudioGain (Gain);
				return -1;
			} else {
				aafi->ctx.current_clip_gain = Gain;
				aafi->ctx.clips_using_gain  = 0;
			}
		}

		DUMP_OBJ (aafi, ConstantValue, &__td);

	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioPan) &&
	           aafUIDCmp (ParamDef, &AAFParameterDef_Pan)) {
		/*
		 * Pan automation shall be track-based. If an application has a different
		 * native representation (e.g., clip-based pan), it shall convert to and
		 * from its native representation when exporting and importing the composition.
		 */

		aafIndirect_t* Indirect = aaf_get_propertyValue (ConstantValue, PID_ConstantValue_Value, &AAFTypeID_Indirect);

		if (Indirect == NULL) {
			DUMP_OBJ_ERROR (aafi, ConstantValue, &__td, "Missing PID_ConstantValue_Value or wrong AAFTypeID");
			return -1;
		}

		aafRational_t* multiplier = aaf_get_indirectValue (aafi->aafd, Indirect, &AAFTypeID_Rational);

		if (multiplier == NULL) {
			DUMP_OBJ_ERROR (aafi, ConstantValue, &__td, "Could not retrieve Indirect value for PID_ConstantValue_Value");
			return -1;
		}

		// if ( multiplier == NULL ) {
		// 	DUMP_OBJ_ERROR( aafi, ConstantValue, &__td, "Missing PID_ConstantValue_Value or wrong AAFTypeID" );
		// 	return -1;
		// }

		aafiAudioPan* Pan = calloc (sizeof (aafiAudioPan), sizeof (unsigned char));

		Pan->pts_cnt = 1;
		Pan->value   = calloc (1, sizeof (aafRational_t*));
		Pan->flags |= AAFI_AUDIO_GAIN_CONSTANT;

		memcpy (&Pan->value[0], multiplier, sizeof (aafRational_t));

		/* Pan is Track-based only. */
		aafi->ctx.current_track->pan = Pan;

		DUMP_OBJ (aafi, ConstantValue, &__td);
	} else {
		DUMP_OBJ_NO_SUPPORT (aafi, ConstantValue, &__td);
	}

	return 0;
}

static int
parse_VaryingValue (AAF_Iface* aafi, aafObject* VaryingValue, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	// __td.sub = 1;

	if (!aaf_get_propertyValue (VaryingValue->Parent, PID_OperationGroup_InputSegments, &AAFTypeID_SegmentStrongReferenceVector)) {
		__td.eob = 1;
	}

	aafUID_t* ParamDef = aaf_get_propertyValue (VaryingValue, PID_Parameter_Definition, &AAFTypeID_AUID);

	if (ParamDef == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, VaryingValue, &__td, "Missing PID_Parameter_Definition");
		return -1;
	}

	aafUID_t* OperationIdentification = get_OperationGroup_OperationIdentification (aafi, VaryingValue->Parent);

	if (OperationIdentification == NULL) {
		DUMP_OBJ_ERROR (aafi, VaryingValue, &__td, "Could not retrieve OperationIdentification");
		return -1;
	}

	aafiInterpolation_e interpolation               = 0;
	aafUID_t*           InterpolationIdentification = get_Parameter_InterpolationIdentification (aafi, VaryingValue);

	if (InterpolationIdentification == NULL) {
		DUMP_OBJ_WARNING (aafi, VaryingValue, &__td, "Could not retrieve InterpolationIdentification: Setting to Linear");
		interpolation = AAFI_INTERPOL_LINEAR;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_None)) {
		interpolation = AAFI_INTERPOL_NONE;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_Linear)) {
		interpolation = AAFI_INTERPOL_LINEAR;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_Power)) {
		interpolation = AAFI_INTERPOL_POWER;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_Constant)) {
		interpolation = AAFI_INTERPOL_CONSTANT;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_BSpline)) {
		interpolation = AAFI_INTERPOL_BSPLINE;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_Log)) {
		interpolation = AAFI_INTERPOL_LOG;
	} else {
		DUMP_OBJ_WARNING (aafi, VaryingValue, &__td, "Unknown value for InterpolationIdentification: Falling back to Linear");
		interpolation = AAFI_INTERPOL_LINEAR;
	}

	aafObject* Points = aaf_get_propertyValue (VaryingValue, PID_VaryingValue_PointList, &AAFTypeID_ControlPointStrongReferenceVector);

	if (Points == NULL) {
		/*
		 * Some files like the ProTools and LogicPro break standard by having no
		 * PointList entry for AAFOperationDef_MonoAudioGain.
		 */

		DUMP_OBJ_WARNING (aafi, VaryingValue, &__td, "Missing PID_VaryingValue_PointList or list is empty");
		return -1;
	}

	// if ( aafUIDCmp( VaryingValue->Parent->Parent->Class->ID, &AAFClassID_Transition ) )
	if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioDissolve) &&
	    aafUIDCmp (ParamDef, &AAFParameterDef_Level)) {
		aafiTransition* Trans = aafi->ctx.current_transition;

		Trans->flags |= interpolation;
		Trans->pts_cnt_a = retrieve_ControlPoints (aafi, Points, &(Trans->time_a), &(Trans->value_a));

		if (Trans->pts_cnt_a < 0) {
			/* In that case, parse_OperationGroup() will set transition to default. */
			DUMP_OBJ_ERROR (aafi, VaryingValue, &__td, "Could not retrieve ControlPoints");
			return -1;
		}

		// for ( int i = 0; i < Trans->pts_cnt_a; i++ ) {
		// 	debug( "time_%i : %i/%i   value_%i : %i/%i", i, Trans->time_a[i].numerator, Trans->time_a[i].denominator, i, Trans->value_a[i].numerator, Trans->value_a[i].denominator  );
		// }

		DUMP_OBJ (aafi, VaryingValue, &__td);
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioGain) &&
	           aafUIDCmp (ParamDef, &AAFParameterDef_Amplitude)) {
		aafiAudioGain* Gain = calloc (sizeof (aafiAudioGain), sizeof (unsigned char));

		Gain->flags |= interpolation;
		Gain->pts_cnt = retrieve_ControlPoints (aafi, Points, &Gain->time, &Gain->value);

		if (Gain->pts_cnt < 0) {
			DUMP_OBJ_ERROR (aafi, VaryingValue, &__td, "Could not retrieve ControlPoints");
			free (Gain);
			return -1;
		}

		// for ( int i = 0; i < Gain->pts_cnt; i++ ) {
		// 	debug( "time_%i : %i/%i   value_%i : %i/%i", i, Gain->time[i].numerator, Gain->time[i].denominator, i, Gain->value[i].numerator, Gain->value[i].denominator );
		// }

		/* If gain has 2 ControlPoints with both the same value, it means
		 * we have a flat gain curve. So we can assume constant gain here. */

		if (Gain->pts_cnt == 2 &&
		    (Gain->value[0].numerator == Gain->value[1].numerator) &&
		    (Gain->value[0].denominator == Gain->value[1].denominator)) {
			if (aafRationalToFloat (Gain->value[0]) == 1.0f) {
				/*
				 * gain is null, skip it. Skipping it allows not to set a useless gain then miss the real clip gain later (Resolve 18.5.AAF)
				 */
				aafi_freeAudioGain (Gain);
				return -1;
			}
			Gain->flags |= AAFI_AUDIO_GAIN_CONSTANT;
		} else {
			Gain->flags |= AAFI_AUDIO_GAIN_VARIABLE;
		}

		/*
		 * Loop through ancestors to find out who is the parent of OperationGroup.
		 * If it belongs to TimelineMobSlot, that means the Parameter is attached
		 * to a Track. If it belongs to a Component, the Parameter is attached to
		 * a clip.
		 *
		 * NOTE: We can't just check the Parent since we can have nested OperationGroups
		 * providing different effects like Pan, Gain, CustomFx.. Therefore looping
		 * is required.
		 */

		aafObject* Obj = VaryingValue->Parent; // Start with the last OperationGroup
		for (; Obj != NULL && aafUIDCmp (Obj->Class->ID, &AAFClassID_ContentStorage) == 0; Obj = Obj->Parent)
			if (!aafUIDCmp (Obj->Class->ID, &AAFClassID_OperationGroup))
				break;

		if (aafUIDCmp (Obj->Class->ID, &AAFClassID_TimelineMobSlot)) {
			/* Track-based Gain */

			if (aafi->ctx.current_track->gain) {
				DUMP_OBJ_ERROR (aafi, VaryingValue, &__td, "Track Gain was already set");
				aafi_freeAudioGain (Gain);
				return -1;
			} else {
				aafi->ctx.current_track->gain = Gain;
				DUMP_OBJ (aafi, VaryingValue, &__td);
			}
		} else {
			/* Clip-based Gain */

			if (Gain->flags & AAFI_AUDIO_GAIN_CONSTANT) {
				if (aafi->ctx.current_clip_gain) {
					DUMP_OBJ_ERROR (aafi, VaryingValue, &__td, "Clip gain was already set");
					aafi_freeAudioGain (Gain);
					return -1;
				} else {
					aafi->ctx.current_clip_gain = Gain;
					aafi->ctx.clips_using_gain  = 0;
				}
			} else {
				if (aafi->ctx.current_clip_automation) {
					DUMP_OBJ_ERROR (aafi, VaryingValue, &__td, "Clip automation was already set");
					aafi_freeAudioGain (Gain);
					return -1;
				} else {
					aafi->ctx.current_clip_automation = Gain;
					aafi->ctx.clips_using_automation  = 0;
				}
			}
		}
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioPan) &&
	           aafUIDCmp (ParamDef, &AAFParameterDef_Pan)) {
		/*
		 * Pan automation shall be track-based. If an application has a different
		 * native representation (e.g., clip-based pan), it shall convert to and
		 * from its native representation when exporting and importing the composition.
		 */

		aafiAudioPan* Pan = calloc (sizeof (aafiAudioPan), sizeof (unsigned char));

		Pan->flags |= AAFI_AUDIO_GAIN_VARIABLE;
		Pan->flags |= interpolation;

		Pan->pts_cnt = retrieve_ControlPoints (aafi, Points, &Pan->time, &Pan->value);

		if (Pan->pts_cnt < 0) {
			DUMP_OBJ_ERROR (aafi, VaryingValue, &__td, "Could not retrieve ControlPoints");
			free (Pan);
			return -1;
		}

		// for ( int i = 0; i < Gain->pts_cnt; i++ ) {
		// 	debug( "time_%i : %i/%i   value_%i : %i/%i", i, Gain->time[i].numerator, Gain->time[i].denominator, i, Gain->value[i].numerator, Gain->value[i].denominator  );
		// }

		/* If Pan has 2 ControlPoints with both the same value, it means
		 * we have a constant Pan curve. So we can assume constant Pan here. */

		if (Pan->pts_cnt == 2 &&
		    (Pan->value[0].numerator == Pan->value[1].numerator) &&
		    (Pan->value[0].denominator == Pan->value[1].denominator)) {
			// if ( aafRationalToFloat(Gain->value[0]) == 1.0f ) {
			// 	/*
			// 	 * Pan is null, skip it. Skipping it allows not to set a useless gain then miss the real clip gain later (Resolve 18.5.AAF)
			// 	 */
			// 	aafi_freeAudioGain( Gain );
			// 	return -1;
			// }
			Pan->flags |= AAFI_AUDIO_GAIN_CONSTANT;
		} else {
			Pan->flags |= AAFI_AUDIO_GAIN_VARIABLE;
		}

		if (aafi->ctx.current_track->pan) {
			DUMP_OBJ_ERROR (aafi, VaryingValue, &__td, "Track Pan was already set");
			aafi_freeAudioGain (Pan);
			return -1;
		} else {
			aafi->ctx.current_track->pan = Pan;
			DUMP_OBJ (aafi, VaryingValue, &__td);
		}
	} else {
		DUMP_OBJ (aafi, VaryingValue, &__td);
	}

	return 0;
}

static int
retrieve_ControlPoints (AAF_Iface* aafi, aafObject* Points, aafRational_t* times[], aafRational_t* values[])
{
	*times  = calloc (Points->Header->_entryCount, sizeof (aafRational_t));
	*values = calloc (Points->Header->_entryCount, sizeof (aafRational_t));

	aafObject* Point = NULL;

	unsigned int i = 0;

	aaf_foreach_ObjectInSet (&Point, Points, &AAFClassID_ControlPoint)
	{
		aafRational_t* time = aaf_get_propertyValue (Point, PID_ControlPoint_Time, &AAFTypeID_Rational);

		if (time == NULL) {
			// aafi_trace_obj( aafi, Points, ANSI_COLOR_RED );
			error ("Missing ControlPoint::Time.");

			free (*times);
			*times = NULL;
			free (*values);
			*values = NULL;

			return -1;
		}

		// aafRational_t *value = aaf_get_propertyIndirectValue( Point, PID_ControlPoint_Value, &AAFTypeID_Rational );

		aafIndirect_t* Indirect = aaf_get_propertyValue (Point, PID_ControlPoint_Value, &AAFTypeID_Indirect);

		if (Indirect == NULL) {
			// DUMP_OBJ_ERROR( aafi, ConstantValue, &__td, "Missing PID_ConstantValue_Value or wrong AAFTypeID" );
			error ("Missing ControlPoint::Value or wrong AAFTypeID");

			free (*times);
			*times = NULL;
			free (*values);
			*values = NULL;

			return -1;
		}

		aafRational_t* value = aaf_get_indirectValue (aafi->aafd, Indirect, &AAFTypeID_Rational);

		if (value == NULL) {
			// aafi_trace_obj( aafi, Points, ANSI_COLOR_RED );
			error ("Could not retrieve Indirect value for PID_ControlPoint_Value");

			free (*times);
			*times = NULL;
			free (*values);
			*values = NULL;

			return -1;
		}

		memcpy ((*times + i), time, sizeof (aafRational_t));
		memcpy ((*values + i), value, sizeof (aafRational_t));

		i++;
	}

	if (Points->Header->_entryCount != i) {
		// aafi_trace_obj( aafi, Points, ANSI_COLOR_YELLOW );
		warning ("Points _entryCount (%i) does not match iteration (%i).", Points->Header->_entryCount, i);
		return i;
	}

	// aafi_trace_obj( aafi, Points, ANSI_COLOR_MAGENTA );

	return Points->Header->_entryCount;
}

/* ****************************************************************************
 *                                 M o b
 * ****************************************************************************

 *                            Mob (abs)
 *                             |
 *                             |--> CompositionMob
 *                             |--> MasterMob
 *                             `--> SourceMob
 */

static int
parse_Mob (AAF_Iface* aafi, aafObject* Mob)
{
	// int dl = DUMP_INIT(aafi);
	// __td(NULL);
	static td __td;
	__td.fn    = __LINE__;
	__td.pfn   = 0;
	__td.lv    = 0;
	__td.ll    = calloc (1024, sizeof (int)); /* TODO free */
	__td.ll[0] = 0;
	// aafi->ctx.trace_leveloop = __td.ll; // keep track of __td.ll for free

	aafObject* MobSlots = aaf_get_propertyValue (Mob, PID_Mob_Slots, &AAFTypeID_MobSlotStrongReferenceVector);

	if (MobSlots == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, Mob, &__td, "Missing PID_Mob_Slots");
		free (__td.ll);
		return -1;
	}

	if (aafUIDCmp (Mob->Class->ID, &AAFClassID_CompositionMob)) {
		aafUID_t* UsageCode = aaf_get_propertyValue (Mob, PID_Mob_UsageCode, &AAFTypeID_UsageType);

		if (aafUIDCmp (UsageCode, &AAFUsage_AdjustedClip)) {
			DUMP_OBJ_ERROR (aafi, Mob, &__td, "Skipping AAFUsage_AdjustedClip");
			return -1;
		}

		parse_CompositionMob (aafi, Mob, &__td);
	} else if (aafUIDCmp (Mob->Class->ID, &AAFClassID_MasterMob)) {
		DUMP_OBJ (aafi, Mob, &__td);
	} else if (aafUIDCmp (Mob->Class->ID, &AAFClassID_SourceMob)) {
		DUMP_OBJ (aafi, Mob, &__td);
	}

	/*
	 * Loops through MobSlots
	 */

	aafObject* MobSlot = NULL;

	int i = 0;

	aaf_foreach_ObjectInSet (&MobSlot, MobSlots, NULL)
	{
		__td.ll[__td.lv] = MobSlots->Header->_entryCount - i++;
		parse_MobSlot (aafi, MobSlot, &__td);
	}

	free (__td.ll);

	return 0;
}

static int
parse_CompositionMob (AAF_Iface* aafi, aafObject* CompoMob, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	DUMP_OBJ (aafi, CompoMob, &__td);

	aafi->compositionName = aaf_get_propertyValue (CompoMob, PID_Mob_Name, &AAFTypeID_String); /* opt */

	aafObject* UserComment  = NULL;
	aafObject* UserComments = aaf_get_propertyValue (CompoMob, PID_Mob_UserComments, &AAFTypeID_TaggedValueStrongReferenceVector); /* opt */

	aaf_foreach_ObjectInSet (&UserComment, UserComments, NULL)
	{
		/* TODO: implement retrieve_TaggedValue() ? */

		wchar_t* name = aaf_get_propertyValue (UserComment, PID_TaggedValue_Name, &AAFTypeID_String);

		if (name == NULL) { /* req */
			DUMP_OBJ_ERROR (aafi, UserComment, &__td, "Missing PID_TaggedValue_Name");
			continue;
		}

		aafIndirect_t* Indirect = aaf_get_propertyValue (UserComment, PID_TaggedValue_Value, &AAFTypeID_Indirect);

		if (Indirect == NULL) {
			DUMP_OBJ_ERROR (aafi, UserComment, &__td, "Missing PID_TaggedValue_Value");
			continue;
		}

		wchar_t* text = aaf_get_indirectValue (aafi->aafd, Indirect, &AAFTypeID_String);

		if (text == NULL) {
			DUMP_OBJ_ERROR (aafi, UserComment, &__td, "Could not retrieve Indirect value for PID_TaggedValue_Value");
			continue;
		}

		aafiUserComment* Comment = aafi_newUserComment (aafi, &aafi->Comments);

		Comment->name = name;
		Comment->text = text;
	}

	return 0;
}

static int
parse_SourceMob (AAF_Iface* aafi, aafObject* SourceMob, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	__td.hc = 1;

	/* TODO find a better way to check if we're parsing audio */

	// aafUID_t *DataDefinition = get_Component_DataDefinition( aafi, SourceMob );
	//
	// if ( DataDefinition == NULL ) /* req */ {
	// 	error( "Could not retrieve MobSlot::Segment DataDefinition." );
	// 	return -1;
	// }
	//
	//
	//
	// if ( aafUIDCmp( DataDefinition, &AAFDataDef_Sound ) ||
	//      aafUIDCmp( DataDefinition, &AAFDataDef_LegacySound ) )

	if (aafi->ctx.current_essence) {
		if (aafi->ctx.current_essence == NULL) {
			DUMP_OBJ_ERROR (aafi, SourceMob, &__td, "ctx->current_essence no set");
			return -1;
		}

		aafiAudioEssence* audioEssence = (aafiAudioEssence*)aafi->ctx.current_essence;

		aafMobID_t* MobID = aaf_get_propertyValue (SourceMob, PID_Mob_MobID, &AAFTypeID_MobIDType);

		if (MobID == NULL) {
			DUMP_OBJ_ERROR (aafi, SourceMob, &__td, "Missing PID_Mob_MobID");
			return -1;
		}

		memcpy (audioEssence->umid, MobID, sizeof (aafMobID_t));

		aafTimeStamp_t* CreationTime = aaf_get_propertyValue (SourceMob, PID_Mob_CreationTime, &AAFTypeID_TimeStamp);

		if (CreationTime == NULL) {
			DUMP_OBJ_ERROR (aafi, SourceMob, &__td, "Missing PID_Mob_CreationTime");
			return -1;
		}

		snprintf (audioEssence->originationDate, sizeof (audioEssence->originationDate), "%04u:%02u:%02u",
		          (CreationTime->date.year <= 9999) ? CreationTime->date.year : 0,
		          (CreationTime->date.month <= 99) ? CreationTime->date.month : 0,
		          (CreationTime->date.day <= 99) ? CreationTime->date.day : 0);

		snprintf (audioEssence->originationTime, sizeof (audioEssence->originationTime), "%02u:%02u:%02u",
		          (CreationTime->time.hour <= 99) ? CreationTime->time.hour : 0,
		          (CreationTime->time.minute <= 99) ? CreationTime->time.minute : 0,
		          (CreationTime->time.second <= 99) ? CreationTime->time.second : 0);
	}

	aafObject* EssenceDesc = aaf_get_propertyValue (SourceMob, PID_SourceMob_EssenceDescription, &AAFTypeID_EssenceDescriptorStrongReference);

	if (EssenceDesc == NULL) {
		DUMP_OBJ_ERROR (aafi, SourceMob, &__td, "Could not retrieve EssenceDescription");
		return -1;
	}

	DUMP_OBJ (aafi, SourceMob, &__td);

	parse_EssenceDescriptor (aafi, EssenceDesc, &__td);

	return 0;
}

static aafiAudioTrack*
get_audio_track_by_tracknumber (AAF_Iface* aafi, int tracknumber)
{
	aafiAudioTrack* audioTrack = NULL;
	int             count      = 0;

	foreach_audioTrack (audioTrack, aafi)
	{
		if (++count == tracknumber)
			return audioTrack;
	}

	return NULL;
}

/* ****************************************************************************
 *                             M o b S l o t
 * ****************************************************************************
 *
 *                          MobSlot (abs)
 *                             |
 *                             |--> TimelineMobSlot
 *                             |--> EventMobSlot
 *                             `--> StaticMobSlot
 */

static int
parse_MobSlot (AAF_Iface* aafi, aafObject* MobSlot, td* __ptd)
{
	// debug( "MS : %p", __ptd->ll );

	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	__td.hc = 1;

	aafObject* Segment = aaf_get_propertyValue (MobSlot, PID_MobSlot_Segment, &AAFTypeID_SegmentStrongReference);

	if (Segment == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, MobSlot, &__td, "Missing PID_MobSlot_Segment");
		return -1;
	}

	aafUID_t* DataDefinition = get_Component_DataDefinition (aafi, Segment);

	if (DataDefinition == NULL) { /* req */
		DUMP_OBJ_ERROR (aafi, MobSlot, &__td, "Could not retrieve DataDefinition");
		return -1;
	}

	aafPosition_t session_end = 0;

	if (aafUIDCmp (MobSlot->Class->ID, &AAFClassID_TimelineMobSlot)) {
		/*
		 * Each TimelineMobSlot represents a track, either audio or video.
		 *
		 * The Timeline MobSlot::Segment should hold a Sequence of Components.
		 * This Sequence represents the timeline track. Therefore, each SourceClip
		 * contained in the Sequence::Components represents a clip on the timeline.
		 *
		 * CompositionMob can have TimelineMobSlots, StaticMobSlots, EventMobSlots
		 */

		aafRational_t* edit_rate = aaf_get_propertyValue (MobSlot, PID_TimelineMobSlot_EditRate, &AAFTypeID_Rational);

		if (edit_rate == NULL) { /* req */
			DUMP_OBJ_ERROR (aafi, MobSlot, &__td, "Missing PID_TimelineMobSlot_EditRate");
			return -1;
		}

		if (aafUIDCmp (/*aafi->ctx.*/ MobSlot->Parent->Class->ID, &AAFClassID_CompositionMob)) {
			/*
			 * There should be only one Composition, since a CompositionMob represents the overall
			 * composition (i.e project). Observations on files confirm that.
			 *
			 * However, the AAF Edit Protocol says that there could be multiple CompositionMobs
			 * (Mob::UsageCode TopLevel), containing other CompositionMobs (Mob::UsageCode LowerLevel).
			 * This was not encountered yet, even on Avid exports with AAF_EditProtocol enabled.
			 *
			 * TODO: update note
			 * TODO: implement multiple TopLevel compositions support
			 */

			if (aafUIDCmp (DataDefinition, &AAFDataDef_Sound) ||
			    aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
				/*
				 * p.11 : In a CompositionMob or MasterMob, PhysicalTrackNumber is the
				 * output channel number that the MobSlot should be routed to when played.
				 */

				if (!aafi->ctx.is_inside_derivation_chain) {
					uint32_t  tracknumber = 0;
					uint32_t* track_num   = aaf_get_propertyValue (MobSlot, PID_MobSlot_PhysicalTrackNumber, &AAFTypeID_UInt32);

					if (track_num == NULL) { /* opt */
						tracknumber = aafi->Audio->track_count + 1;
					} else {
						tracknumber = *track_num;
					}

					aafiAudioTrack* track = get_audio_track_by_tracknumber (aafi, tracknumber);

					if (!track) {
						track = aafi_newAudioTrack (aafi);
					}

					track->number = tracknumber;

					aafi->Audio->track_count += 1;

					aafi->ctx.current_track = track;

					track->name = aaf_get_propertyValue (MobSlot, PID_MobSlot_SlotName, &AAFTypeID_String);

					track->edit_rate = edit_rate;
				}

				// aaf_dump_ObjectProperties( aafi->aafd, MobSlot );

				/*
				 * The following seems to be ProTools proprietary.
				 * If a track is multi-channel, it specifies its format : 2 (stereo), 6 (5.1) or 8 (7.1).
				 *
				 * In the current implementation we don't need this. We guess the format at the OperationGroup level with the
				 * AAFOperationDef_AudioChannelCombiner OperationDefinition, which also looks to be ProTools specific.
				 */

				// aafPID_t PIDTimelineMobAttributeList = aaf_get_PropertyIDByName( aafi->aafd, "TimelineMobAttributeList" );
				// // aafPID_t PIDTimelineMobAttributeList = aaf_get_PropertyIDByName( aafi->aafd, "MobAttributeList" );
				//
				// if ( PIDTimelineMobAttributeList != 0 ) {
				// 	aafObject *TaggedValues = aaf_get_propertyValue( aafi->ctx.MobSlot, PIDTimelineMobAttributeList );
				// 	aafObject *TaggedValue  = NULL;
				//
				// 	aaf_foreach_ObjectInSet( &TaggedValue, TaggedValues, NULL ) {
				// 		char *name = aaf_get_propertyValueText( TaggedValue, PID_TaggedValue_Name );
				//
				// 		debug( "TaggedValue %s", name );
				//
				// 		if ( strncmp( "_TRACK_FORMAT", name, 13 ) == 0 ) {
				// 			uint32_t *format = (uint32_t*)aaf_get_propertyIndirectValue( TaggedValue, PID_TaggedValue_Value );
				//
				// 			if ( format != NULL )
				// 				aafi->ctx.current_track->format = *format;
				//
				// 			debug( "Format : %u", aafi->ctx.current_track->format );
				// 		}
				//
				// 		free( name );
				// 	}
				// }

				DUMP_OBJ (aafi, MobSlot, &__td);

				/* Reset timeline position */
				// aafi->ctx.current_track->current_pos = 0;

				aafi_parse_Segment (aafi, Segment, &__td);

				/* update session_end if needed */
				// session_end = ( aafi->ctx.current_pos > session_end ) ? aafi->ctx.current_pos : session_end;
				// debug( "AAFIParser 4286: Current pos : %lu\n", aafi->ctx.current_track->current_pos );
				session_end = (aafi->ctx.current_track->current_pos > session_end) ? aafi->ctx.current_track->current_pos : session_end;

				// debug( "SESSIon_end : %li", session_end );

			} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Timecode) ||
			           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyTimecode)) {
				DUMP_OBJ (aafi, MobSlot, &__td);
				aafi_parse_Segment (aafi, Segment, &__td);
			} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Picture) ||
			           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyPicture)) {
				// debug( "%ls", aaft_ClassIDToText(aafi->aafd, Segment->Class->ID) );

				/* ADP NESTED SCOPE */

				// aafObject *NSSegment = NULL;
				// aafObject *NSSegments = aaf_get_propertyValue( Segment, PID_NestedScope_Slots );
				//
				// aaf_foreach_ObjectInSet( &NSSegment, NSSegments, NULL ) {
				// 	aaf_dump_ObjectProperties( aafi->aafd, NSSegment );
				// }

				if (aafi->Video->Tracks) {
					DUMP_OBJ_ERROR (aafi, MobSlot, &__td, "Current implementation supports only one video track");
					return -1;
				}

				/*
				 * p.11 : In a CompositionMob or MasterMob, PhysicalTrackNumber is the output channel number that the
				 * MobSlot should be routed to when played.
				 */

				uint32_t  tracknumber = 0;
				uint32_t* track_num   = aaf_get_propertyValue (MobSlot, PID_MobSlot_PhysicalTrackNumber, &AAFTypeID_UInt32);

				if (track_num == NULL) { /* opt */
					tracknumber = 1; /* Current implementation supports only one video track. */
				} else {
					tracknumber = *track_num;
				}

				aafiVideoTrack* track = aafi_newVideoTrack (aafi);

				track->number = tracknumber;

				track->name = aaf_get_propertyValue (MobSlot, PID_MobSlot_SlotName, &AAFTypeID_String);

				track->edit_rate = edit_rate;

				DUMP_OBJ (aafi, MobSlot, &__td);

				aafi_parse_Segment (aafi, Segment, &__td);

				/* update session_end if needed */
				// session_end = ( aafi->ctx.current_track->current_pos > session_end ) ? aafi->ctx.current_track->current_pos : session_end;
			} else {
				DUMP_OBJ_NO_SUPPORT (aafi, MobSlot, &__td);
			}

		} else if (aafUIDCmp (MobSlot->Parent->Class->ID, &AAFClassID_MasterMob)) {
			/* Retrieve Essences */

			// if ( aafUIDCmp( DataDefinition, &AAFDataDef_Sound ) ||
			//      aafUIDCmp( DataDefinition, &AAFDataDef_LegacySound ) )
			// {
			DUMP_OBJ (aafi, MobSlot, &__td);
			aafi_parse_Segment (aafi, Segment, &__td);

			// }
			// else if ( aafUIDCmp( DataDefinition, &AAFDataDef_Picture ) ||
			//           aafUIDCmp( DataDefinition, &AAFDataDef_LegacyPicture ) )
			// {
			// 	aafi_parse_Segment( aafi, Segment );
			//
			// 	retrieve_EssenceData( aafi );
			// }
			// else {
			// 	aafi_trace_obj( aafi, MobSlot, ANSI_COLOR_YELLOW );
			// 	debug( "%ls", aaft_DataDefToText( aafi->aafd, DataDefinition ) );
			// }
		} else if (aafUIDCmp (MobSlot->Parent->Class->ID, &AAFClassID_SourceMob)) {
			if (aafi->ctx.current_essence != NULL) {
				aafiAudioEssence* audioEssence = (aafiAudioEssence*)aafi->ctx.current_essence;

				aafPosition_t* Origin = aaf_get_propertyValue (MobSlot, PID_TimelineMobSlot_Origin, &AAFTypeID_PositionType);

				if (Origin == NULL) { /* req */
					DUMP_OBJ_ERROR (aafi, MobSlot, &__td, "Missing PID_TimelineMobSlot_Origin");
					return -1;
				}

				audioEssence->timeReference   = *Origin;
				audioEssence->mobSlotEditRate = edit_rate;

				DUMP_OBJ (aafi, MobSlot, &__td);
			} else {
				DUMP_OBJ_ERROR (aafi, MobSlot, &__td, "aafi->ctx.current_essence no set");
			}
		} else {
			/* Not in CompositionMob, MasterMob, or SourceMob. Can not happen. */
			DUMP_OBJ_NO_SUPPORT (aafi, MobSlot, &__td);
		}

	} else if (aafUIDCmp (MobSlot->Class->ID, &AAFClassID_EventMobSlot)) {
		aafRational_t* edit_rate = aaf_get_propertyValue (MobSlot, PID_EventMobSlot_EditRate, &AAFTypeID_Rational);

		if (edit_rate == NULL) { /* req */
			DUMP_OBJ_ERROR (aafi, MobSlot, &__td, "Missing PID_EventMobSlot_EditRate");
			return -1;
		}

		aafi->ctx.current_markers_edit_rate = edit_rate;

		DUMP_OBJ (aafi, MobSlot, &__td);

		return aafi_parse_Segment (aafi, Segment, &__td);
	} else {
		/* Not AAFClassID_TimelineMobSlot */
		DUMP_OBJ_NO_SUPPORT (aafi, MobSlot, &__td);
	}

	/* TODO implement global (audio and video) session start and end */
	// if ( aafi->ctx.current_tree_type == AAFI_TREE_TYPE_AUDIO )

	if (session_end > 0 && aafi->Timecode && aafi->Timecode->end < session_end) {
		aafi->Timecode->end = session_end;
	}

	// if ( aafi->ctx.current_tree_type == AAFI_TREE_TYPE_VIDEO )
	// 	if ( session_end > 0 && aafi->Video->tc )
	// 		aafi->Video->tc->end = session_end;
	// else
	// 	error( "MISSING aafiTimecode !" );

	return 0;
}

int
aafi_retrieveData (AAF_Iface* aafi)
{
	aafObject* Mob = NULL;

	aaf_foreach_ObjectInSet (&Mob, aafi->aafd->Mobs, &AAFClassID_CompositionMob)
	{
		aafUID_t* UsageCode = aaf_get_propertyValue (Mob, PID_Mob_UsageCode, &AAFTypeID_UsageType);

		if (aafUIDCmp (aafi->aafd->Header.OperationalPattern, &AAFOPDef_EditProtocol) &&
		    !aafUIDCmp (UsageCode, &AAFUsage_TopLevel)) {
			/*
			 * If we run against AAFOPDef_EditProtocol, we process only TopLevels CompositionMobs.
			 * If there is more than one, we have multiple Compositions in a single AAF.
			 */

			// aafi_trace_obj( aafi, Mob, ANSI_COLOR_RED );

			// // aaf_dump_ObjectProperties( aafi->aafd, aafi->ctx.Mob );
			//
			// aafObject *MobSlots = aaf_get_propertyValue( aafi->ctx.Mob, PID_Mob_Slots, &AAFTypeID_MobSlotStrongReferenceVector );
			// aafObject *MobSlot = NULL;
			// uint32_t SlotID = 0;
			//
			// aaf_foreach_ObjectInSet( &MobSlot, MobSlots, NULL ) {
			// 	aaf_dump_ObjectProperties( aafi->aafd, MobSlot );
			// }

			continue;
		}

		RESET_CONTEXT (aafi->ctx);

		parse_Mob (aafi, Mob);
	}

	// aafiAudioTrack   *audioTrack = NULL;
	// aafiTimelineItem *audioItem  = NULL;
	// // aafiAudioClip    *audioClip  = NULL;
	//
	// // uint32_t i = 0;
	//
	// foreach_audioTrack( audioTrack, aafi ) {
	// 	foreach_Item( audioItem, audioTrack ) {
	//
	// 		if ( audioItem->type == AAFI_TRANS ) {
	// 			continue;
	// 		}
	//
	// 		aafiAudioClip *audioClip = (aafiAudioClip*)&audioItem->data;
	//
	// 		if ( audioClip->masterMobID && !audioClip->Essence ) {
	// 			debug( "E m p t y   C l i p" );
	//
	// 			aafObject *Mob = NULL;
	//
	// 			aaf_foreach_ObjectInSet( &Mob, aafi->aafd->Mobs, NULL ) {
	// 				/* loops through Mobs */
	// 				aafUID_t *UsageCode = aaf_get_propertyValue( Mob, PID_Mob_UsageCode, &AAFTypeID_ );
	//
	// 				aafMobID_t *MobID = aaf_get_propertyValue( Mob, PID_Mob_MobID, &AAFTypeID_MobIDType );
	//
	// 				if ( !aafMobIDCmp( MobID, audioClip->masterMobID ) ) {
	// 					continue;
	// 				}
	//
	// 				// aaf_dump_ObjectProperties( aafi->aafd, Mob );
	// 				debug( "Clip SourceID      : %ls", aaft_MobIDToText(MobID) );
	//
	// 				debug( "PointedMob ClassID : %ls", aaft_ClassIDToText(aafi->aafd, Mob->Class->ID) );
	// 				debug( "PointedMob UsageCd : %ls", aaft_UsageCodeToText(UsageCode) );
	// 				debug( "PointedMob Name    : %ls", aaf_get_propertyValue(Mob, PID_Mob_Name, &AAFTypeID_String) );
	//
	//
	//
	// 				aafObject *MobSlots = aaf_get_propertyValue( Mob, PID_Mob_Slots, &AAFTypeID_MobSlotStrongReferenceVector );
	// 				aafObject *MobSlot = NULL;
	// 				int SlotID = 1;
	// 				aaf_foreach_ObjectInSet( &MobSlot, MobSlots, NULL ) {
	// 					debug( "  SlotID %u", SlotID );
	//
	// 					aafObject *Segment = aaf_get_propertyValue( MobSlot, PID_MobSlot_Segment, &AAFTypeID_SegmentStrongReference );
	//
	// 					if ( Segment == NULL ) {
	// 						error( "Missing MobSlot::Segment." );
	// 						return -1;
	// 					}
	//
	//
	// 					aafUID_t *DataDefinition = get_Component_DataDefinition( aafi, Segment );
	//
	// 					if ( DataDefinition == NULL ) {
	// 						error( "Could not retrieve MobSlot::Segment DataDefinition." );
	// 						return -1;
	// 					}
	//
	// 					// CURRENTPOINTER
	//
	// 					debug( "    Segment : %ls", aaft_ClassIDToText( aafi->aafd, Segment->Class->ID ) );
	// 					debug( "    DataDefinition : %ls", aaft_DataDefToText(aafi->aafd, DataDefinition) );
	//
	//
	//
	//
	//
	//
	// 					if ( aafUIDCmp( Segment->Class->ID, &AAFClassID_SourceClip ) ) {
	// 						aafMobID_t *SourceID = aaf_get_propertyValue( Segment, PID_SourceReference_SourceID, &AAFTypeID_MobIDType );
	// 						debug( "     SourceID : %ls", aaft_MobIDToText(sourceID) );
	// 					}
	//
	// 					SlotID++;
	// 				}
	//
	//
	// 			}
	// 		}
	// 	}
	// }

	// aaf_foreach_ObjectInSet( &(aafi->ctx.Mob), aafi->aafd->Mobs, &AAFClassID_SourceMob ) {
	//
	// 	aafObject *MobSlots = aaf_get_propertyValue( aafi->ctx.Mob, PID_Mob_Slots, &AAFTypeID_MobSlotStrongReferenceVector );
	//
	// 	aaf_foreach_ObjectInSet( &(aafi->ctx.MobSlot), MobSlots, NULL ) {
	//
	// 		/*
	// 		 * Check if the SourceMob was parsed.
	// 		 * If it was not, we can print the trace.
	// 		 *
	// 		 * NOTE We do it after the main loop, so we make sure all MasterMobs was parsed.
	// 		 */
	//
	// 		aafObject *Segment = aaf_get_propertyValue( aafi->ctx.MobSlot, PID_MobSlot_Segment, &AAFTypeID_SegmentStrongReference );
	//
	// 		aafUID_t  *DataDefinition = get_Component_DataDefinition( aafi, Segment );
	//
	// 		aafMobID_t *MobID = aaf_get_propertyValue( aafi->ctx.Mob, PID_Mob_MobID, &AAFTypeID_MobIDType );
	//
	// 		aafiAudioEssence *audioEssence = NULL;
	//
	// 		foreachEssence( audioEssence, aafi->Audio->Essences ) {
	// 			if ( aafMobIDCmp( MobID, audioEssence->sourceMobID ) )
	// 				break;
	// 		}
	//
	// 		if ( audioEssence == NULL ) {
	// 			aafi_trace_obj( aafi, aafi->ctx.MobSlot, ANSI_COLOR_YELLOW );
	// 			debug( "%ls", aaft_DataDefToText( aafi->aafd, DataDefinition ) );
	// 		}
	//
	// 	}
	// }

	if (aafi->Timecode == NULL) {
		warning ("No timecode found in file. Setting to 00:00:00:00 @ 25fps");

		aafiTimecode* tc = calloc (sizeof (aafiTimecode), sizeof (unsigned char));

		if (tc == NULL) {
			error ("calloc() : %s", strerror (errno));
			return -1;
		}

		tc->start     = 0;
		tc->fps       = 25;
		tc->drop      = 0;
		tc->edit_rate = &AAFI_DEFAULT_TC_EDIT_RATE;

		aafi->Timecode = tc;
	}

	/* aafi->Audio->tc->end is set to composition duration. Add tc->start to set composition end time */

	if (aafi->Timecode && aafi->Timecode->end) {
		aafi->Timecode->end += aafi->Timecode->start;
	}

	/* Post processing */

	/* TODO move to parse_*() */
	/* Parse summary descriptor (WAVE/AIFC) if any */

	aafiAudioEssence* audioEssence = NULL;

	foreachEssence (audioEssence, aafi->Audio->Essences)
	{
		/* TODO: rename (not only summary, can be external file too) */
		aafi_parse_audio_summary (aafi, audioEssence);

		/* TODO : check samplerate / samplesize proportions accross essences, and choose the most used values as composition values */
		if (aafi->Audio->samplerate == 0 || aafi->Audio->samplerate == audioEssence->samplerate) {
			aafi->Audio->samplerate = audioEssence->samplerate;
		} else {
			// warning( "audioEssence '%ls' has different samplerate : %u", audioEssence->file_name, audioEssence->samplerate );
		}

		if (aafi->Audio->samplesize == 0 || aafi->Audio->samplesize == audioEssence->samplesize) {
			aafi->Audio->samplesize = audioEssence->samplesize;
		} else {
			// warning( "audioEssence '%ls' has different samplesize : %i", audioEssence->file_name, audioEssence->samplesize );
		}
	}

	aafiAudioTrack* audioTrack = NULL;

	foreach_audioTrack (audioTrack, aafi)
	{
		// aafiTimelineItem *audioItem  = NULL;
		// aafiAudioClip    *audioClip  = NULL;
		//
		// if ( audioTrack->format > 1 ) {
		//
		// 	foreach_Item( audioItem, audioTrack ) {
		//
		// 		if ( audioItem->type == AAFI_TRANS ) {
		// 			continue;
		// 		}
		//
		// 		audioClip = (aafiAudioClip*)&audioItem->data;
		// 	}
		// }

		if (audioTrack->current_pos > aafi->Audio->length) {
			aafi->Audio->length                      = audioTrack->current_pos;
			aafi->Audio->length_editRate.numerator   = audioTrack->edit_rate->numerator;
			aafi->Audio->length_editRate.denominator = audioTrack->edit_rate->denominator;
		}
	}

	aafiVideoTrack* videoTrack = NULL;

	foreach_videoTrack (videoTrack, aafi)
	{
		if (videoTrack->current_pos > aafi->Video->length) {
			aafi->Video->length                      = videoTrack->current_pos;
			aafi->Video->length_editRate.numerator   = videoTrack->edit_rate->numerator;
			aafi->Video->length_editRate.denominator = videoTrack->edit_rate->denominator;
		}
	}

	if (aafi->Audio->length > aafi->Video->length) {
		aafi->compositionLength                      = aafi->Audio->length;
		aafi->compositionLength_editRate.numerator   = aafi->Audio->length_editRate.numerator;
		aafi->compositionLength_editRate.denominator = aafi->Audio->length_editRate.denominator;
	} else {
		aafi->compositionLength                      = aafi->Video->length;
		aafi->compositionLength_editRate.numerator   = aafi->Video->length_editRate.numerator;
		aafi->compositionLength_editRate.denominator = aafi->Video->length_editRate.denominator;
	}

	aafi->compositionStart                      = aafi->Timecode->start;
	aafi->compositionStart_editRate.numerator   = aafi->Timecode->edit_rate->numerator;
	aafi->compositionStart_editRate.denominator = aafi->Timecode->edit_rate->denominator;

	if (protools_AAF (aafi)) {
		protools_post_processing (aafi);
	}

	return 0;
}

/**
 * @}
 */
