/*
 * Copyright (C) 2017-2024 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.
 */

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

#include "aaf/AAFCore.h"
#include "aaf/AAFDump.h"
#include "aaf/AAFToText.h"
#include "aaf/AAFTypes.h"

#include "aaf/AAFDefs/AAFClassDefUIDs.h"
#include "aaf/AAFDefs/AAFFileKinds.h"
#include "aaf/AAFDefs/AAFPropertyIDs.h"
#include "aaf/AAFDefs/AAFTypeDefUIDs.h"

#include "aaf/log.h"

#include "aaf/AAFClass.h"
#include "aaf/utils.h"

#define debug(...) \
	AAF_LOG (aafd->log, aafd, LOG_SRC_ID_AAF_CORE, VERB_DEBUG, __VA_ARGS__)

#define warning(...) \
	AAF_LOG (aafd->log, aafd, LOG_SRC_ID_AAF_CORE, VERB_WARNING, __VA_ARGS__)

#define error(...) \
	AAF_LOG (aafd->log, aafd, LOG_SRC_ID_AAF_CORE, VERB_ERROR, __VA_ARGS__)

/**
 * Loops through each aafStrongRefSetEntry_t of a StrongRefSet Index node stream.
 *
 * @param Header Pointer to the stream's aafStrongRefSetHeader_t struct.
 * @param Entry  Pointer that will receive each aafStrongRefSetEntry_t struct.
 * @param i      uint32_t iterator.
 */

#define foreachStrongRefSetEntry(Header, Entry, i)                                                                                                                                                                         \
	for (i = 0;                                                                                                                                                                                                        \
	     i < Header->_entryCount &&                                                                                                                                                                                    \
	     memcpy (&Entry, ((char*)(Header)) + (sizeof (aafStrongRefSetHeader_t) + (Header->_identificationSize + sizeof (aafStrongRefSetEntry_t)) * i), sizeof (aafStrongRefSetEntry_t) + Header->_identificationSize); \
	     i++)

/**
 * Loops through each aafStrongRefVectorEntry_t of a StrongRefVector Index node stream.
 *
 * @param Header Pointer to the stream's aafStrongRefVectorHeader_t struct.
 * @param Entry  Pointer that will receive each aafStrongRefVectorEntry_t struct.
 * @param i      uint32_t iterator.
 */
#define foreachStrongRefVectorEntry(vectorStream, Header, Entry, i)                                                                                                  \
	for (i = 0;                                                                                                                                                  \
	     i < Header._entryCount &&                                                                                                                               \
	     memcpy (&Entry, (vectorStream + (sizeof (aafStrongRefVectorHeader_t) + (sizeof (aafStrongRefVectorEntry_t) * i))), sizeof (aafStrongRefVectorEntry_t)); \
	     i++)

#define attachNewProperty(Class, PDef, Pid, IsReq)  \
	PDef = calloc (1, sizeof (aafPropertyDef)); \
	if (!PDef) {                                \
		error ("Out of memory");            \
		return NULL;                        \
	}                                           \
	PDef->pid         = Pid;                    \
	PDef->isReq       = IsReq;                  \
	PDef->meta        = 0;                      \
	PDef->name        = NULL;                   \
	PDef->next        = Class->Properties;      \
	Class->Properties = PDef;

/*
 * Retrieves useful file informations out of Header Object.
 *
 * @param  aafd  Pointer to the AAF_Data structure.
 *
 * @return        0 on success\n
 *               -1 on error.
 */

static int
parse_Header (AAF_Data* aafd);

/*
 * Retrieves useful file informations out of Identification Object.
 *
 * @param  aafd  Pointer to the AAF_Data structure.
 *
 * @return        0 on success\n
 *               -1 on error.
 */

static int
parse_Identification (AAF_Data* aafd);

/*
 * Tests the CFB_Data.hdr._clsid field for a valid AAF file.
 *
 * @note The spec says that the AAFFileKind signature should be retrieved from the CLSID
 * of the Root IStorage. In practice, this CLSID holds the #AAFClassID_Root value, and
 * the AAFFileKind is (sometimes) found in the CLSID of the CFB_Header, which according
 * to the CFB spec should be zero. All of this has been observed in AAF files built with
 * the official AAFSDK.
 *
 * As a conclusion, the way to test for a valid AAF file is not a valid test itself.. or
 * not sufficiently documented. Thus, this function shall not be trusted until further
 * knowledge improvement.
 *
 * @param  aafd  Pointer to the AAF_Data structure.
 *
 * @return       1 if the file *looks like* a valid AAF\n
 *               0 otherwise.
 */

// static int isValidAAF( AAF_Data *aafd );

/**
 * Sets the AAF_Data structure's pointers to the main AAF Tree objects. These pointers
 * can then be used for quick conveniant objects access.
 *
 * @param  aafd   Pointer to the AAF_Data structure.
 */

static void
setObjectShortcuts (AAF_Data* aafd);

/**
 * Parses the entire Compound File Binary Tree and retrieves Objets and Properties.
 *
 * This function first parses the Root Object, then follows the Root::MetaDictionary to
 * retrieve potential custom Classes and Properties with retrieveMetaDictionaryClass(),
 * and then parses the rest of the Tree starting at Root::Header.
 *
 * This function should be called after the AAF Classes has been defined by
 * #aafclass_setDefaultClasses(). This function is called by aaf_load_file().
 *
 * @param  aafd   Pointer to the AAF_Data structure.
 */

static int
retrieveObjectTree (AAF_Data* aafd);

/**
 * Parses the entire MetaDictionary, retrieving potential custom classes and properties.
 * This function is to be called within a loop that iterates through the
 * MetaDictionary::ClassDefinitions Objects, and by itself when looking for parent
 * Classes.
 *
 * @param  aafd           Pointer to the AAF_Data structure.
 * @param  TargetClassDef Pointer to the current ClassDefinition Object.
 *
 * @return                A pointer to the retrieved Class.
 */

static aafClass*
retrieveMetaDictionaryClass (AAF_Data* aafd, aafObject* TargetClassDef);

/**
 * Allocates a new aafObject structure, and adds it to the AAF_Data.Objects list.
 *
 * @param  aafd   Pointer to the AAF_Data structure.
 * @param  node   Pointer to the corresponding cfbNode structure.
 * @param  Class  Pointer to the corresponding class definition aafClass structure.
 * @param  parent Pointer to the new Object's parent object, that is the one that has an
 *                ownership reference (Strong Ref Set/Vector) to the new Object.
 *
 * @return        A pointer to the newly created aafObject.
 */

static aafObject*
newObject (AAF_Data* aafd, cfbNode* node, aafClass* Class, aafObject* parent);

/**
 * Allocates a new aafProperty structure.
 *
 * @param  Def  Pointer to the corresponding property definition aafPropertyDef structure.
 *
 * @return      A pointer to the newly created aafProperty.
 */

static aafProperty*
newProperty (AAF_Data* aafd, aafPropertyDef* Def);

/**
 * Test whether or not, a property ID (property definition) was already retrieved for a given Class.
 *
 * @param  Class  Pointer to the aafClass.
 * @param  Pid    Property ID.
 *
 * @return        A pointer to the retrieved property definition\n
 *                 NULL otherwise.
 */

static aafPropertyDef*
propertyIdExistsInClass (aafClass* Class, aafPID_t Pid);

/**
 * Sets the aafStrongRefSetHeader_t Obj->Header and aafStrongRefSetEntry_t Obj->Entry,
 * when parsing an Object from a StrongReferenceSet. This function is called by the
 * #retrieveStrongReferenceSet() function.
 *
 * @param  Obj    Pointer to an aafObject structure.
 * @param  Header Pointer to an aafStrongRefSetHeader_t structure.
 * @param  Entry  Pointer to an aafStrongRefSetEntry_t structure.
 */

static int
setObjectStrongRefSet (aafObject* Obj, aafStrongRefSetHeader_t* Header, aafStrongRefSetEntry_t* Entry);

/**
 * Sets the aafStrongRefVectorHeader_t Obj->Header and aafStrongRefVectorEntry_t
 * Obj->Entry, when parsing an Object from a StrongReferenceSet. This function is called
 * by the retrieveStrongReferenceVector() function.
 *
 * @param  Obj    Pointer to an aafObject structure.
 * @param  Header Pointer to an aafStrongRefVectorHeader_t structure.
 * @param  Entry  Pointer to an aafStrongRefVectorEntry_t structure.
 */

static int
setObjectStrongRefVector (aafObject* Obj, aafStrongRefVectorHeader_t* Header, aafStrongRefVectorEntry_t* Entry);

/**
 * Retrieves and parses a single StrongReference Object. This function is called by
 * retrieveProperty() when it encounters an SF_STRONG_OBJECT_REFERENCE property.
 *
 * @param aafd   Pointer to the AAF_Data structure.
 * @param Prop   Pointer to the property holding the SF_STRONG_OBJECT_REFERENCE.
 * @param parent Pointer to the parent Object which holds the Prop property.
 */

static int
retrieveStrongReference (AAF_Data* aafd, aafProperty* Prop, aafObject* Parent);

/**
 * Retrieves and parses StrongReferenceSet Objects. This function is called by
 * retrieveProperty() when it encounters an SF_STRONG_OBJECT_REFERENCE_SET property.
 *
 * @param aafd   Pointer to the AAF_Data structure.
 * @param Prop   Pointer to the property holding the SF_STRONG_OBJECT_REFERENCE_SET.
 * @param parent Pointer to the parent Object which holds the Prop property.
 */

static int
retrieveStrongReferenceSet (AAF_Data* aafd, aafProperty* Prop, aafObject* parent);

/**
 * Retrieve and parse StrongReferenceVector Objects. This function is called by
 * retrieveProperty() when it encounters an SF_STRONG_OBJECT_REFERENCE_VECTOR property.
 *
 * @param aafd   Pointer to the AAF_Data structure.
 * @param Prop   Pointer to the property holding the SF_STRONG_OBJECT_REFERENCE_VECTOR.
 * @param parent Pointer to the parent Object which holds the Prop property.
 */

static int
retrieveStrongReferenceVector (AAF_Data* aafd, aafProperty* Prop, aafObject* Parent);

/**
 * Adds a new aafProperty to an Object->properties list. If the property Stored Form is
 * either SF_STRONG_OBJECT_REFERENCE, SF_STRONG_OBJECT_REFERENCE_SET or
 * SF_STRONG_OBJECT_REFERENCE_VECTOR, then the function follows the "link" to the
 * Object(s) by calling respectively retrieveStrongReference(),
 * retrieveStrongReferenceSet() or retrieveStrongReferenceVector(). This function is
 * called by retrieveObjectProperties().
 *
 * @param aafd Pointer to the AAF_Data structure.
 * @param Obj  Pointer to the aafObject structure holding this property.
 * @param Def  Pointer to the aafPropertyDef structure defining this property.
 * @param p    Pointer to the aafPropertyIndexEntry_t structure representing the property
 *             in the file.
 * @param v    Pointer to a p->_length long byte array holding the actual property value.
 * @param bo   uint8_t specifying the property's Byte Order. TO BE IMPLEMENTED
 *
 * @TODO Take ByteOrder into account
 */

static int
retrieveProperty (AAF_Data* aafd, aafObject* Obj, aafPropertyDef* Def, aafPropertyIndexEntry_t* p, aafByte_t* v, uint8_t bo);

/**
 * Retrieves the properties for a given aafObject.
 *
 * @param aafd Pointer to the AAF_Data structure.
 * @param Obj  Pointer to the aafObject holding the properties.
 */

static int
retrieveObjectProperties (AAF_Data* aafd, aafObject* Obj);

/**
 * Retrieves a StrongRef Set/Vector Index Node in the Compound File Tree. This function
 * is called by both retrieveStrongReferenceSet() and retrieveStrongReferenceVector().
 *
 * @param  aafd    Pointer to the AAF_Data structure.
 * @param  parent  Pointer to the parent aafObject.
 * @param  refName Pointer to a null terminated string holding the reference name.
 *
 * @return         Pointer to the retrieved Node cfbNode structure.
 */

static cfbNode*
getStrongRefIndexNode (AAF_Data* aafd, aafObject* Parent, const char* refName);

/**
 * Retrieves a StrongRef Set or Vector Entry Node in the Compound File Tree. This
 * function is called by both retrieveStrongReferenceSet() and
 * retrieveStrongReferenceVector().
 *
 * @param aafd     Pointer to the AAF_Data structure.
 * @param parent   Pointer to the parent Index Node.
 * @param baseName Pointer to a null terminated string holding the reference base name.
 * @param index    uint32_t number representing the index number of the reference.
 *
 * @return         Pointer to the retrieved Node cfbNode structure.
 */

static cfbNode*
getStrongRefEntryNode (AAF_Data* aafd, aafObject* Parent, const char* refName, uint32_t index);

/**
 * Retrieves and returns a list of aafPropertyIndexHeader_t.
 * For a given cfbNode, retrieves its /properties Stream Node and returns the stream as
 * a pointer to an aafPropertyIndexHeader_t structure, wich the stream should begin with.
 *
 * @param  aafd Pointer to the AAF_Data structure.
 * @param  node Pointer to a cfbNode structure.
 *
 * @return      Pointer to an aafPropertyIndexHeader_t structure, followed by _entryCount
 *              aafPropertyIndexEntry_t structures.
 */

static aafByte_t*
getNodeProperties (AAF_Data* aafd, cfbNode* node);

/**
 * Retrieves and returns a list of StrongReferenceSet.
 *
 * For a given Index cfbNode, retrieves its Stream and returns it as a pointer to an
 * aafStrongRefSetHeader_t structure, wich the stream should begin with.
 *
 * @param aafd   Pointer to the AAF_Data structure.
 * @param node   Pointer to an Index cfbNode structure.
 * @param parent Pointer to the aafObject parent, only used on error printing.
 *
 * @return       Pointer to an aafStrongRefSetHeader_t structure, followed by _entryCount
 *               aafStrongRefSetEntry_t structures.
 */

static aafStrongRefSetHeader_t*
getStrongRefSetList (AAF_Data* aafd, cfbNode* Node, aafObject* Parent);

/**
 * Retrieves and returns a list of StrongReferenceVectors.
 *
 * For a given Index cfbNode, retrieves its Stream and returns it as a pointer to an
 * aafStrongRefVectorHeader_t structure, wich the stream should begin with.
 *
 * @param  aafd   Pointer to the AAF_Data structure.
 * @param  node   Pointer to an Index cfbNode structure.
 * @param  parent Pointer to the aafObject parent, only used on error printing.
 *
 * @return        Pointer to an aafStrongRefVectorHeader_t structure, followed by
 *                _entryCount aafStrongRefVectorEntry_t structures.
 */

static aafByte_t*
getStrongRefVectorList (AAF_Data* aafd, cfbNode* Node, aafObject* Parent);

AAF_Data*
aaf_alloc (struct aafLog* log)
{
	AAF_Data* aafd = calloc (1, sizeof (AAF_Data));

	if (!aafd) {
		goto err;
	}

	aafd->cfbd = NULL;

	aafd->Identification.CompanyName          = NULL;
	aafd->Identification.ProductName          = NULL;
	aafd->Identification.ProductVersionString = NULL;
	aafd->Identification.Platform             = NULL;

	aafd->Classes = NULL;
	aafd->Objects = NULL;
	aafd->log     = log;

	aafd->cfbd = cfb_alloc (log);

	if (!aafd->cfbd) {
		goto err;
	}

	return aafd;

err:

	if (aafd) {
		if (aafd->cfbd) {
			cfb_release (&aafd->cfbd);
		}
		free (aafd);
	}

	return NULL;
}

int
aaf_load_file (AAF_Data* aafd, const char* file)
{
	if (!aafd || !file)
		return 1;

	aafd->Objects = NULL;
	aafd->Classes = NULL;

	if (cfb_load_file (&aafd->cfbd, file) < 0) {
		return 1;
	}

	/*
	 * NOTE: at least Avid Media Composer doesn't respect
	 * the standard clsid AAFFileKind_Aaf4KBinary identifier.
	 * Therefore isValidAAF() is useless until futher findings..
	 */

	// if ( isValidAAF( aafd ) == 0 ) {
	// 	return 1;
	// }

	if (aafclass_setDefaultClasses (aafd) < 0) {
		return -1;
	}

	if (retrieveObjectTree (aafd) < 0) {
		return -1;
	}

	if (parse_Header (aafd) < 0) {
		return -1;
	}

	if (parse_Identification (aafd) < 0) {
		return -1;
	}

	return 0;
}

void
aaf_release (AAF_Data** aafd)
{
	if (!aafd || !(*aafd))
		return;

	if ((*aafd)->cfbd != NULL)
		cfb_release (&((*aafd)->cfbd));

	aafClass* Class    = NULL;
	aafClass* tmpClass = NULL;

	for (Class = (*aafd)->Classes; Class != NULL; Class = tmpClass) {
		tmpClass = Class->next;

		aafPropertyDef* PDef    = NULL;
		aafPropertyDef* tmpPDef = NULL;

		free (Class->name);

		for (PDef = Class->Properties; PDef != NULL; PDef = tmpPDef) {
			tmpPDef = PDef->next;

			free (PDef->name);
			free (PDef);
		}

		free (Class);
	}

	aafObject* Object    = NULL;
	aafObject* tmpObject = NULL;

	for (Object = (*aafd)->Objects; Object != NULL; Object = tmpObject) {
		tmpObject = Object->nextObj;

		free (Object->Header);
		free (Object->Entry);
		free (Object->Name);

		aafProperty* Prop    = NULL;
		aafProperty* tmpProp = NULL;

		for (Prop = Object->Properties; Prop != NULL; Prop = tmpProp) {
			tmpProp = Prop->next;

			switch (Prop->sf) {
				case SF_STRONG_OBJECT_REFERENCE:
				case SF_STRONG_OBJECT_REFERENCE_SET:
				case SF_STRONG_OBJECT_REFERENCE_VECTOR:
					break;

				default:
					free (Prop->val);
			}

			free (Prop);
		}

		free (Object);
	}

	free ((*aafd)->Identification.CompanyName);
	free ((*aafd)->Identification.ProductName);
	free ((*aafd)->Identification.ProductVersionString);
	free ((*aafd)->Identification.Platform);

	free (*aafd);

	*aafd = NULL;
}

char*
aaf_get_ObjectPath (aafObject* Obj)
{
	static char path[CFB_PATH_NAME_SZ];

	uint32_t offset = CFB_PATH_NAME_SZ;
	path[--offset]  = '\0';

	while (Obj != NULL) {
		for (int i = (int)strlen (Obj->Name) - 1; i >= 0 && offset > 0; i--) {
			path[--offset] = Obj->Name[i];
		}

		if (offset == 0)
			break;

		path[--offset] = '/';

		Obj = Obj->Parent;
	}

	return path + offset;
}

int
_aaf_foreach_ObjectInSet (aafObject** Obj, aafObject* head, const aafUID_t* filter)
{
	if (!(*Obj))
		*Obj = head;
	else
		*Obj = (*Obj)->next;

	if (filter != NULL)
		for (; *Obj != NULL; *Obj = (*Obj)->next)
			if (aafUIDCmp ((*Obj)->Class->ID, filter))
				break;

	return (!(*Obj)) ? 0 : 1;
}

aafObject*
aaf_get_ObjectByWeakRef (aafObject* list, aafWeakRef_t* ref)
{
	if (!ref || !list || !list->Entry) {
		return NULL;
	}

	AAF_Data* aafd = list->aafd;

	/* Target is a Reference Vector */
	if (list->Header->_identificationSize == 0) {
		// debug( "Has local key" );

		for (; list != NULL; list = list->next) {
			if (list->Entry->_localKey == ref->_referencedPropertyIndex) {
				// debug( "list->Entry->_localKey            : 0x%x", list->Entry->_localKey );
				// debug( "list->Header->_identificationSize : %u", list->Header->_identificationSize );
				// debug( "FOUND : 0x%x", list->Entry->_localKey );
				return list;
			}
		}
	}
	/* Target is a Reference Set */
	else {
		for (; list != NULL; list = list->next) {
			if (memcmp (list->Entry->_identification, ref->_identification, ref->_identificationSize) == 0) {
				if (list->Header->_identificationSize != ref->_identificationSize) {
					/* TODO : is it possible ? is it an error ? */
					debug ("list->Header->_identificationSize (%i bytes) doesn't match ref->_identificationSize (%i bytes)", list->Header->_identificationSize, ref->_identificationSize);
				}

				return list;
			}
		}
	}

	return NULL;
}

aafUID_t*
aaf_get_InterpolationIdentificationByWeakRef (AAF_Data* aafd, aafWeakRef_t* InterpolationDefWeakRef)
{
	aafObject* InterpolationDefinition = aaf_get_ObjectByWeakRef (aafd->InterpolationDefinition, InterpolationDefWeakRef);

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

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

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

	return InterpolationIdentification;
}

aafUID_t*
aaf_get_OperationIdentificationByWeakRef (AAF_Data* aafd, aafWeakRef_t* OperationDefWeakRef)
{
	aafObject* OperationDefinition = aaf_get_ObjectByWeakRef (aafd->OperationDefinition, OperationDefWeakRef);

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

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

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

	return OperationIdentification;
}

aafUID_t*
aaf_get_ContainerIdentificationByWeakRef (AAF_Data* aafd, aafWeakRef_t* ContainerDefWeakRef)
{
	aafObject* ContainerDefinition = aaf_get_ObjectByWeakRef (aafd->ContainerDefinition, ContainerDefWeakRef);

	if (!ContainerDefinition) {
		warning ("Could not retrieve WeakRef from Dictionary::ContainerDefinitions.");
		return NULL;
	}

	aafUID_t* ContainerIdentification = aaf_get_propertyValue (ContainerDefinition, PID_DefinitionObject_Identification, &AAFTypeID_AUID);

	if (!ContainerIdentification) {
		warning ("Missing ContainerDefinition's DefinitionObject::Identification.");
		return NULL;
	}

	return ContainerIdentification;
}

aafUID_t*
aaf_get_DataIdentificationByWeakRef (AAF_Data* aafd, aafWeakRef_t* DataDefWeakRef)
{
	aafObject* DataDefinition = aaf_get_ObjectByWeakRef (aafd->DataDefinition, DataDefWeakRef);

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

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

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

	return DataIdentification;
}

aafObject*
aaf_get_ObjectAncestor (aafObject* Obj, const aafUID_t* ClassID)
{
	/*
	 * 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.
	 */

	while (Obj != NULL && !aafUIDCmp (Obj->Class->ID, &AAFClassID_ContentStorage)) {
		if (aafUIDCmp (ClassID, Obj->Class->ID)) {
			return Obj;
		}

		if (aaf_ObjectInheritsClass (Obj, ClassID)) {
			return Obj;
		}

		Obj = Obj->Parent;
	}

	return NULL;
}

int
aaf_ObjectInheritsClass (aafObject* Obj, const aafUID_t* classID)
{
	// AAF_Data *aafd = Obj->aafd;

	aafClass* classObj = NULL;
	foreachClassInheritance (classObj, Obj->Class)
	{
		if (aafUIDCmp (classObj->ID, classID)) {
			// debug( "%s is a parent of class %s",
			// 	aaft_ClassIDToText( aafd, classObj->ID ),
			// 	aaft_ClassIDToText( aafd, Obj->Class->ID ) );
			return 1;
		}
	}
	return 0;
}

aafObject*
aaf_get_MobByID (aafObject* Mobs, aafMobID_t* MobID)
{
	aafObject* Mob = NULL;

	if (!MobID)
		return NULL;

	AAF_foreach_ObjectInSet (&Mob, Mobs, NULL)
	{
		aafMobID_t* Current = aaf_get_propertyValue (Mob, PID_Mob_MobID, &AAFTypeID_MobIDType);

		if (!Current || aafMobIDCmp (Current, MobID))
			break;
	}

	return Mob;
}

aafObject*
aaf_get_MobSlotBySlotID (aafObject* MobSlots, aafSlotID_t SlotID)
{
	aafObject* MobSlot = NULL;

	AAF_foreach_ObjectInSet (&MobSlot, MobSlots, NULL)
	{
		aafSlotID_t* CurrentSlotID = aaf_get_propertyValue (MobSlot, PID_MobSlot_SlotID, &AAFTypeID_UInt32);

		if (!CurrentSlotID || *CurrentSlotID == SlotID)
			break;
	}

	return MobSlot;
}

aafObject*
aaf_get_EssenceDataByMobID (AAF_Data* aafd, aafMobID_t* MobID)
{
	aafMobID_t* DataMobID   = NULL;
	aafObject*  EssenceData = NULL;

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

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

	return EssenceData;
}

aafUID_t*
aaf_get_ParamDefIDByName (AAF_Data* aafd, const char* name)
{
	aafUID_t*  ParamDefIdent        = NULL;
	aafObject* ParameterDefinitions = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_ParameterDefinitions, &AAFTypeID_ParameterDefinitionStrongReferenceSet);
	aafObject* ParameterDefinition  = NULL;

	AAF_foreach_ObjectInSet (&ParameterDefinition, ParameterDefinitions, NULL)
	{
		char* paramName = aaf_get_propertyValue (ParameterDefinition, PID_DefinitionObject_Name, &AAFTypeID_String);

		if (!paramName) {
			continue;
		}

		if (strcmp (paramName, name) == 0) {
			ParamDefIdent = aaf_get_propertyValue (ParameterDefinition, PID_DefinitionObject_Identification, &AAFTypeID_AUID);
			free (paramName);
			break;
		}

		free (paramName);
	}

	return ParamDefIdent;
}

void*
aaf_get_TaggedValueByName (AAF_Data* aafd, aafObject* TaggedValueVector, const char* name, const aafUID_t* type)
{
	struct aafLog* log = aafd->log;

	aafObject* TaggedValue = NULL;

	AAF_foreach_ObjectInSet (&TaggedValue, TaggedValueVector, NULL)
	{
		if (!aafUIDCmp (TaggedValue->Class->ID, &AAFClassID_TaggedValue)) {
			LOG_BUFFER_WRITE (log, "     %sObject > %s\n",
			                  ANSI_COLOR_RESET (log),
			                  aaft_ClassIDToText (aafd, TaggedValue->Class->ID));
			continue;
		}

		char*          taggedName     = aaf_get_propertyValue (TaggedValue, PID_TaggedValue_Name, &AAFTypeID_String);
		aafIndirect_t* taggedIndirect = aaf_get_propertyValue (TaggedValue, PID_TaggedValue_Value, &AAFTypeID_Indirect);

		if (strcmp (taggedName, name) == 0) {
			if (aafUIDCmp (&taggedIndirect->TypeDef, type)) {
				debug ("Found TaggedValue \"%s\" of type %s",
				       taggedName,
				       aaft_TypeIDToText (&taggedIndirect->TypeDef));

				free (taggedName);

				void* value = aaf_get_indirectValue (aafd, taggedIndirect, type);

				return value;
			}

			debug ("Got TaggedValue \"%s\" but of type %s instead of %s",
			       taggedName,
			       aaft_TypeIDToText (&taggedIndirect->TypeDef),
			       aaft_TypeIDToText (type));
		}
		// LOG_BUFFER_WRITE( log, "     %sTagged > Name: %s%s%s%*s      Value: %s(%s)%s %s%s%s",
		// 	ANSI_COLOR_RESET(log),
		// 	ANSI_COLOR_DARKGREY(log),
		// 	(name) ? name : "<unknown>",
		// 	ANSI_COLOR_RESET(log),
		// 	(name) ? (size_t)(24-(int)strlen(name)) : (size_t)(24-strlen("<unknown>")), " ",
		// 	ANSI_COLOR_DARKGREY(log),
		// 	aaft_TypeIDToText( &taggedIndirect->TypeDef ),
		// 	ANSI_COLOR_RESET(log),
		// 	ANSI_COLOR_DARKGREY(log),
		// 	aaft_IndirectValueToText( aafd, taggedIndirect ),
		// 	ANSI_COLOR_RESET(log) );

		free (taggedName);
	}

	debug ("TaggedValue not found \"%s\"", name);

	return NULL;
}

/*
 * TODO Works when the property was retrieved from MetaDictionary. What if the property is standard ?
 */

aafPID_t
aaf_get_PropertyIDByName (AAF_Data* aafd, const char* name)
{
	aafClass* Class = NULL;

	foreachClass (Class, aafd->Classes)
	{
		aafPropertyDef* PDef = NULL;

		foreachPropertyDefinition (PDef, Class->Properties)
		{
			if (PDef->name != NULL && strcmp (PDef->name, name) == 0) {
				return PDef->pid;
			}
		}
	}

	return 0;
}

aafUID_t*
aaf_get_OperationDefIDByName (AAF_Data* aafd, const char* OpDefName)
{
	aafObject* OperationDefinitions = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_OperationDefinitions, &AAFTypeID_OperationDefinitionStrongReferenceSet);
	aafObject* OperationDefinition  = NULL;

	while (_aaf_foreach_ObjectInSet (&OperationDefinition, OperationDefinitions, NULL)) {
		aafUID_t* OpDefIdent = aaf_get_propertyValue (OperationDefinition, PID_DefinitionObject_Identification, &AAFTypeID_AUID);

		char* name = aaf_get_propertyValue (OperationDefinition, PID_DefinitionObject_Name, &AAFTypeID_String);

		if (strcmp (name, OpDefName) == 0) {
			free (name);
			return OpDefIdent;
		}

		free (name);
	}

	return NULL;
}

aafProperty*
aaf_get_property (aafObject* Obj, aafPID_t pid)
{
	if (!Obj)
		return NULL;

	AAF_Data* aafd = Obj->aafd;

	aafProperty* Prop = NULL;

	for (Prop = Obj->Properties; Prop != NULL; Prop = Prop->next)
		if (Prop->pid == pid)
			break;

	if (!Prop) {
		aafPropertyDef* PDef = aafclass_getPropertyDefinitionByID (Obj->Class, pid);

		if (!PDef) {
			warning ("Could not retrieve 0x%04x (%s) of Class %s",
			         pid,
			         aaft_PIDToText (aafd, pid),
			         aaft_ClassIDToText (aafd, Obj->Class->ID));
			return NULL;
		}

		if (PDef->isReq) {
			error ("Could not retrieve %s required property 0x%04x (%s)",
			       aaft_ClassIDToText (aafd, Obj->Class->ID),
			       pid,
			       aaft_PIDToText (aafd, pid));
		} else {
			debug ("Could not retrieve %s optional property 0x%04x (%s)",
			       aaft_ClassIDToText (aafd, Obj->Class->ID),
			       pid,
			       aaft_PIDToText (aafd, pid));
		}
	}

	return Prop;
}

void*
aaf_get_propertyValue (aafObject* Obj, aafPID_t pid, const aafUID_t* typeID)
{
	if (!Obj) {
		return NULL;
	}

	AAF_Data*    aafd = Obj->aafd;
	aafProperty* Prop = aaf_get_property (Obj, pid);

	if (!Prop) {
		return NULL;
	}

	void*    value = Prop->val;
	uint16_t len   = Prop->len;

	if (Prop->sf == SF_DATA_STREAM || aafUIDCmp (typeID, &AAFTypeID_Indirect)) {
		/*
		 * DATA_STREAM stored form and IndirectValues start with a byte identifying byte order : 0x4c, 0x42, 0x55
		 * We must skip that byte.
		 */
		value = (void*)(((char*)value) + 1);
		len--;
	}

	if (aafUIDCmp (typeID, &AAFTypeID_String)) {
		if (((uint16_t*)value)[(len / 2) - 1] != 0x0000) {
			error ("Object %s string property 0x%04x (%s) does not end with NULL",
			       aaft_ClassIDToText (aafd, Obj->Class->ID),
			       pid,
			       aaft_PIDToText (aafd, pid));
			return NULL;
		}

		return cfb_w16toUTF8 (value, len);
	}

	if (aafUIDCmp (typeID, &AAFTypeID_Indirect)) {
		/*
		 * In case of Indirect with string value we check NULL termination here,
		 * because when calling next aaf_get_indirectValue() we wont have access
		 * to Prop->len anymore.
		 */

		aafIndirect_t* Indirect = value;

		if (aafUIDCmp (&Indirect->TypeDef, &AAFTypeID_String) && ((uint16_t*)value)[(len / 2) - 1] != 0x0000) {
			error ("Object %s Indirect::string property 0x%04x (%s) does not end with NULL",
			       aaft_ClassIDToText (aafd, Obj->Class->ID),
			       pid,
			       aaft_PIDToText (aafd, pid));
			return NULL;
		}
	}

	if ((aafUIDCmp (typeID, &AAFTypeID_Boolean) && len != sizeof (aafBoolean_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_Int8) && len != sizeof (int8_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_UInt8) && len != sizeof (uint8_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_Int16) && len != sizeof (int16_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_UInt16) && len != sizeof (uint16_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_Int32) && len != sizeof (int32_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_UInt32) && len != sizeof (uint32_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_Int64) && len != sizeof (int64_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_UInt64) && len != sizeof (uint64_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_PositionType) && len != sizeof (aafPosition_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_LengthType) && len != sizeof (aafLength_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_Rational) && len != sizeof (aafRational_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_TimeStamp) && len != sizeof (aafTimeStamp_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_VersionType) && len != sizeof (aafVersionType_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_ProductVersion) && len != sizeof (aafProductVersion_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_UsageType) && len != sizeof (aafUID_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_AUID) && len != sizeof (aafUID_t)) ||
	    (aafUIDCmp (typeID, &AAFTypeID_MobIDType) && len != sizeof (aafMobID_t))) {
		error ("Object %s property 0x%04x (%s) size (%u) does not match type %s",
		       aaft_ClassIDToText (aafd, Obj->Class->ID),
		       pid,
		       aaft_PIDToText (aafd, pid),
		       len,
		       aaft_TypeIDToText (typeID));
		return NULL;
	}

	return value;
}

void*
aaf_get_indirectValue (AAF_Data* aafd, aafIndirect_t* Indirect, const aafUID_t* typeDef)
{
	if (!Indirect) {
		error ("Indirect is NULL");
		return NULL;
	}

	if (typeDef && aafUIDCmp (&Indirect->TypeDef, typeDef) == 0) {
		error ("Requested Indirect value of type %s but has type %s",
		       aaft_TypeIDToText (typeDef),
		       aaft_TypeIDToText (&Indirect->TypeDef));
		return NULL;
	}

	if (aafUIDCmp (typeDef, &AAFTypeID_String)) {
		/*
		 * Indirect->Value is guaranted by aaf_get_property() to be NULL terminated
		 */

		size_t indirectValueSize = 0;

		for (size_t i = 0; (i % 2 || !(Indirect->Value[i] == 0x00 && Indirect->Value[i + 1] == 0x00)); i++) {
			indirectValueSize++;
		}

		indirectValueSize += 2;

		uint16_t* w16 = malloc (indirectValueSize);

		if (!w16) {
			error ("Out of memory");
			return NULL;
		}

		memcpy (w16, Indirect->Value, indirectValueSize);

		char* str = cfb_w16toUTF8 (w16, indirectValueSize);

		free (w16);

		return str;
	}

	return &Indirect->Value;
}

static int
parse_Header (AAF_Data* aafd)
{
	aafObject* Header = aafd->Header.obj;

	if (!Header) {
		error ("Missing Header Object.");
		return -1;
	}

	int16_t* ByteOrder = aaf_get_propertyValue (Header, PID_Header_ByteOrder, &AAFTypeID_Int16);

	if (!ByteOrder) {
		warning ("Missing Header::ByteOrder.");
	} else {
		aafd->Header.ByteOrder = *ByteOrder;
	}

	aafTimeStamp_t* LastModified = aaf_get_propertyValue (Header, PID_Header_LastModified, &AAFTypeID_TimeStamp);

	if (!LastModified) {
		warning ("Missing Header::LastModified.");
	} else {
		aafd->Header.LastModified = LastModified;
	}

	aafVersionType_t* Version = aaf_get_propertyValue (Header, PID_Header_Version, &AAFTypeID_VersionType);

	if (!Version) {
		warning ("Missing Header::Version.");
	} else {
		aafd->Header.Version = Version;
	}

	uint32_t* ObjectModelVersion = aaf_get_propertyValue (Header, PID_Header_ObjectModelVersion, &AAFTypeID_UInt32);

	if (!ObjectModelVersion) {
		warning ("Missing Header::ObjectModelVersion.");
	} else {
		aafd->Header.ObjectModelVersion = *ObjectModelVersion;
	}

	const aafUID_t* OperationalPattern = aaf_get_propertyValue (Header, PID_Header_OperationalPattern, &AAFTypeID_AUID);

	if (!OperationalPattern) {
		warning ("Missing Header::OperationalPattern.");
		aafd->Header.OperationalPattern = (const aafUID_t*)&AUID_NULL;
	} else {
		aafd->Header.OperationalPattern = OperationalPattern;
	}

	return 0;
}

static int
parse_Identification (AAF_Data* aafd)
{
	aafObject* Identif = aafd->Identification.obj;

	if (!Identif) {
		error ("Missing Identification Object.");
		return -1;
	}

	char* Company = aaf_get_propertyValue (Identif, PID_Identification_CompanyName, &AAFTypeID_String);

	if (!Company) {
		warning ("Missing Identification::CompanyName.");
	} else {
		aafd->Identification.CompanyName = Company;
	}

	char* ProductName = aaf_get_propertyValue (Identif, PID_Identification_ProductName, &AAFTypeID_String);

	if (!ProductName) {
		warning ("Missing Identification::ProductName.");
	} else {
		aafd->Identification.ProductName = ProductName;
	}

	aafProductVersion_t* ProductVersion = aaf_get_propertyValue (Identif, PID_Identification_ProductVersion, &AAFTypeID_ProductVersion);

	if (!ProductVersion) {
		warning ("Missing Identification::ProductVersion.");
	} else {
		aafd->Identification.ProductVersion = ProductVersion;
	}

	char* ProductVersionString = aaf_get_propertyValue (Identif, PID_Identification_ProductVersionString, &AAFTypeID_String);

	if (!ProductVersionString) {
		warning ("Missing Identification::ProductVersionString.");
	} else {
		aafd->Identification.ProductVersionString = ProductVersionString;
	}

	aafUID_t* ProductID = aaf_get_propertyValue (Identif, PID_Identification_ProductID, &AAFTypeID_AUID);

	if (!ProductID) {
		warning ("Missing Identification::ProductID.");
	} else {
		aafd->Identification.ProductID = ProductID;
	}

	aafTimeStamp_t* Date = aaf_get_propertyValue (Identif, PID_Identification_Date, &AAFTypeID_TimeStamp);

	if (!Date) {
		warning ("Missing Identification::Date.");
	} else {
		aafd->Identification.Date = Date;
	}

	aafProductVersion_t* ToolkitVersion = aaf_get_propertyValue (Identif, PID_Identification_ToolkitVersion, &AAFTypeID_ProductVersion);

	if (!ToolkitVersion) {
		warning ("Missing Identification::ToolkitVersion.");
	} else {
		aafd->Identification.ToolkitVersion = ToolkitVersion;
	}

	char* Platform = aaf_get_propertyValue (Identif, PID_Identification_Platform, &AAFTypeID_String);

	if (!Platform) {
		warning ("Missing Identification::Platform.");
	} else {
		aafd->Identification.Platform = Platform;
	}

	aafUID_t* GenerationAUID = aaf_get_propertyValue (Identif, PID_Identification_GenerationAUID, &AAFTypeID_AUID);

	if (!GenerationAUID) {
		warning ("Missing Identification::GenerationAUID.");
	} else {
		aafd->Identification.GenerationAUID = GenerationAUID;
	}

	return 0;
}

// static int isValidAAF( AAF_Data *aafd )
// {
// 	aafUID_t *hdrClsID = (aafUID_t*)&aafd->cfbd->hdr->_clsid;
//
// 	if ( aafUIDCmp( hdrClsID, &AAFFileKind_Aaf512Binary ) ||
// 		 aafUIDCmp( hdrClsID, &AAFFileKind_Aaf4KBinary  ) )
// 			return 1;
//
// //  warning( "Unsuported AAF encoding (%s).", aaft_FileKindToText( hdrClsID ) );
//
// 	return 0;
// }

static void
setObjectShortcuts (AAF_Data* aafd)
{
	//  aafd->Root = aafd->Root;

	aafd->Header.obj = aaf_get_propertyValue (aafd->Root, PID_Root_Header, &AAFUID_NULL);
	//  aafd->MetaDictionary          = aaf_get_propertyValue( aafd->Root,       PID_Root_MetaDictionary                 );

	aafd->ClassDefinition = aaf_get_propertyValue (aafd->MetaDictionary, PID_MetaDictionary_ClassDefinitions, &AAFTypeID_ClassDefinitionStrongReferenceSet);
	aafd->TypeDefinition  = aaf_get_propertyValue (aafd->MetaDictionary, PID_MetaDictionary_TypeDefinitions, &AAFTypeID_TypeDefinitionStrongReferenceSet);

	aafd->Identification.obj = aaf_get_propertyValue (aafd->Header.obj, PID_Header_IdentificationList, &AAFTypeID_IdentificationStrongReferenceVector);
	aafd->Content            = aaf_get_propertyValue (aafd->Header.obj, PID_Header_Content, &AAFTypeID_ContentStorageStrongReference);
	aafd->Dictionary         = aaf_get_propertyValue (aafd->Header.obj, PID_Header_Dictionary, &AAFTypeID_DictionaryStrongReference);

	aafd->Mobs        = aaf_get_propertyValue (aafd->Content, PID_ContentStorage_Mobs, &AAFTypeID_MobStrongReferenceSet);
	aafd->EssenceData = aaf_get_propertyValue (aafd->Content, PID_ContentStorage_EssenceData, &AAFTypeID_EssenceDataStrongReferenceSet);

	aafd->OperationDefinition     = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_OperationDefinitions, &AAFTypeID_OperationDefinitionStrongReferenceSet);
	aafd->ParameterDefinition     = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_ParameterDefinitions, &AAFTypeID_ParameterDefinitionStrongReferenceSet);
	aafd->DataDefinition          = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_DataDefinitions, &AAFTypeID_DataDefinitionStrongReferenceSet);
	aafd->PluginDefinition        = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_PluginDefinitions, &AAFTypeID_PluginDefinitionStrongReferenceSet);
	aafd->CodecDefinition         = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_CodecDefinitions, &AAFTypeID_CodecDefinitionStrongReferenceSet);
	aafd->ContainerDefinition     = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_ContainerDefinitions, &AAFTypeID_ContainerDefinitionStrongReferenceSet);
	aafd->InterpolationDefinition = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_InterpolationDefinitions, &AAFTypeID_InterpolationDefinitionStrongReferenceSet);
	aafd->KLVDataDefinition       = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_KLVDataDefinitions, &AAFTypeID_KLVDataDefinitionStrongReferenceSet);
	aafd->TaggedValueDefinition   = aaf_get_propertyValue (aafd->Dictionary, PID_Dictionary_TaggedValueDefinitions, &AAFTypeID_TaggedValueDefinitionStrongReferenceSet);
}

static int
retrieveObjectTree (AAF_Data* aafd)
{
	int        rc         = 0;
	aafByte_t* propStream = NULL;

	cfbNode* Node = &aafd->cfbd->nodes[0];

	aafClass* Class = aafclass_getClassByID (aafd, (aafUID_t*)&Node->_clsId);

	if (!Class) {
		error ("Could not retrieve class by id");
		goto err;
	}

	aafd->Root = newObject (aafd, Node, Class, NULL);

	if (!aafd->Root) {
		goto err;
	}

	propStream = getNodeProperties (aafd, aafd->Root->Node);

	if (!propStream) {
		error ("Could not retrieve properties for %s.", aaf_get_ObjectPath (aafd->Root));
		goto err;
	}

	aafPropertyIndexHeader_t Header;
	aafPropertyIndexEntry_t  Prop;

	aafPropertyIndexEntry_t AAFHeaderProp;
	aafPropertyIndexEntry_t AAFMetaDcProp;

	memcpy (&Header, propStream, sizeof (aafPropertyIndexHeader_t));

	aafByte_t* AAFHeaderVal = NULL;
	aafByte_t* AAFMetaDcVal = NULL;

	aafByte_t* value = NULL;

	aafPropertyDef* PDef = NULL;

	uint32_t i           = 0;
	size_t   valueOffset = 0;

	foreachPropertyEntry (propStream, Header, Prop, value, valueOffset, i)
	{
		if (Prop._pid == PID_Root_Header) {
			memcpy (&AAFHeaderProp, &Prop, sizeof (aafPropertyIndexEntry_t));
			AAFHeaderVal = value;
		}

		if (Prop._pid == PID_Root_MetaDictionary) {
			memcpy (&AAFMetaDcProp, &Prop, sizeof (aafPropertyIndexEntry_t));
			AAFMetaDcVal = value;
		}
	}

	PDef = aafclass_getPropertyDefinitionByID (aafd->Root->Class, PID_Root_MetaDictionary);

	/* Start recursive parsing of /Root/Header/{*} */

	rc = retrieveProperty (aafd, aafd->Root, PDef, &AAFMetaDcProp, AAFMetaDcVal, Header._byteOrder);

	if (rc < 0) {
		error ("Could not retrieve property %s.", aaft_PIDToText (aafd, PDef->pid));
		goto err;
	}

	/*
	 * Retrieve MetaDictionary.
	 */

	aafObject* MetaDic = aaf_get_propertyValue (aafd->Root, PID_Root_MetaDictionary, &AAFUID_NULL);

	if (!MetaDic) {
		error ("Missing PID_Root_MetaDictionary.");
		goto err;
	}

	aafObject* ClassDefs = aaf_get_propertyValue (MetaDic, PID_MetaDictionary_ClassDefinitions, &AAFTypeID_ClassDefinitionStrongReferenceSet);

	if (!ClassDefs) {
		error ("Missing PID_MetaDictionary_ClassDefinitions.");
		goto err;
	}

	aafObject* ClassDef = NULL;

	AAF_foreach_ObjectInSet (&ClassDef, ClassDefs, NULL)
	{
		retrieveMetaDictionaryClass (aafd, ClassDef);
	}

	PDef = aafclass_getPropertyDefinitionByID (aafd->Root->Class, PID_Root_Header);

	/* Starts recursive parsing of /Root/Header/{*} */

	rc = retrieveProperty (aafd, aafd->Root, PDef, &AAFHeaderProp, AAFHeaderVal, Header._byteOrder);

	if (rc < 0) {
		error ("Could not retrieve property %s.", aaft_PIDToText (aafd, PDef->pid));
		goto err;
	}

	setObjectShortcuts (aafd);

	rc = 0;
	goto end;

err:
	rc = -1;

end:

	free (propStream);

	return rc;
}

static aafClass*
retrieveMetaDictionaryClass (AAF_Data* aafd, aafObject* TargetClassDef)
{
	aafObject* MetaDic = aaf_get_propertyValue (aafd->Root, PID_Root_MetaDictionary, &AAFUID_NULL);

	if (!MetaDic) { /* req */
		debug ("Could not retrieve PID_Root_MetaDictionary property from Root.");
		return NULL;
	}

	aafObject* ClassDefs = aaf_get_propertyValue (MetaDic, PID_MetaDictionary_ClassDefinitions, &AAFTypeID_ClassDefinitionStrongReferenceSet);
	aafObject* ClassDef  = NULL;

	if (!ClassDefs) { /* opt */
		debug ("Could not retrieve PID_MetaDictionary_ClassDefinitions property from MetaDic.");
		return NULL;
	}

	AAF_foreach_ObjectInSet (&ClassDef, ClassDefs, NULL)
	{
		if (ClassDef == TargetClassDef)
			break;
	}

	if (!ClassDef) {
		error ("Could not retrieve ClassDefinition %p", (void*)TargetClassDef);
		return NULL;
	}

	aafUID_t* ClassID = aaf_get_propertyValue (ClassDef, PID_MetaDefinition_Identification, &AAFTypeID_AUID);

	if (!ClassID) { /* req */
		error ("Could not retrieve PID_MetaDefinition_Identification property from ClassDef.");
		return NULL;
	}

	aafWeakRef_t* parent = aaf_get_propertyValue (ClassDef, PID_ClassDefinition_ParentClass, &AAFTypeID_ClassDefinitionWeakReference);

	if (!parent) {
		error ("Could not retrieve PID_ClassDefinition_ParentClass property from ClassDef.");
		return NULL;
	}

	aafObject* Parent = aaf_get_ObjectByWeakRef (ClassDefs, parent);

	if (!Parent) {
		error ("Could not retrieve object by weakRef (PID_ClassDefinition_ParentClass)");
		return NULL;
	}

	aafClass* ParentClass = NULL;

	if (Parent != ClassDef) {
		ParentClass = retrieveMetaDictionaryClass (aafd, Parent);
	} else if (aafUIDCmp (ClassID, &AAFClassID_InterchangeObject) == 0 &&
	           aafUIDCmp (ClassID, &AAFClassID_MetaDefinition) == 0 &&
	           aafUIDCmp (ClassID, &AAFClassID_MetaDictionary) == 0) {
		/*
		 * TODO: what is this ? when does it happen ?
		 */
		error ("Parent's Class equals Child's : %s.", aaft_ClassIDToText (aafd, ClassID));
		return NULL;
	}

	aafClass* Class = aafclass_getClassByID (aafd, ClassID);

	if (!Class) {
		aafBoolean_t* isCon = aaf_get_propertyValue (ClassDef, PID_ClassDefinition_IsConcrete, &AAFTypeID_Boolean);

		if (!isCon) {
			error ("Missing ClassDefinition::IsConcrete.");
			return NULL;
		}

		Class = aafclass_defineNewClass (aafd, ClassID, *isCon, ParentClass);

		if (!Class) {
			error ("Could set new class");
			return NULL;
		}

		Class->meta = 1;
		Class->name = aaf_get_propertyValue (ClassDef, PID_MetaDefinition_Name, &AAFTypeID_String);

		if (!Class->name) {
			debug ("Could not retrieve PID_MetaDefinition_Name property from ClassDef (%s)", aaft_ClassIDToText (aafd, ClassID));
		}
	} else {
		/* if class is standard, we only set its name */
		if (!Class->name) {
			Class->name = aaf_get_propertyValue (ClassDef, PID_MetaDefinition_Name, &AAFTypeID_String);

			if (!Class->name) {
				debug ("Could not retrieve PID_MetaDefinition_Name property from ClassDef (%s)", aaft_ClassIDToText (aafd, ClassID));
			}
		}
	}

	aafObject* Props = aaf_get_propertyValue (ClassDef, PID_ClassDefinition_Properties, &AAFTypeID_PropertyDefinitionStrongReferenceSet);

	if (!Props) { /* opt */
		debug ("Could not retrieve PID_ClassDefinition_Properties property from ClassDef (%s)", aaft_ClassIDToText (aafd, ClassID));
	}

	aafObject* Prop = NULL;

	AAF_foreach_ObjectInSet (&Prop, Props, NULL)
	{
		aafPID_t* Pid = aaf_get_propertyValue (Prop, PID_PropertyDefinition_LocalIdentification, &AAFTypeID_UInt16);

		if (!Pid) {
			error ("Missing PropertyDefinition::LocalIdentification.");
			return NULL;
		}

		aafBoolean_t* isOpt = aaf_get_propertyValue (Prop, PID_PropertyDefinition_IsOptional, &AAFTypeID_Boolean);

		if (!isOpt) {
			error ("Missing PropertyDefinition::IsOptional.");
			return NULL;
		}

		/*
		 * We skip all the properties that were already defined in aafclass_setDefaultClasses().
		 */

		aafPropertyDef* PDef = propertyIdExistsInClass (Class, *Pid);

		if (!PDef) {
			attachNewProperty (Class, PDef, *Pid, (*isOpt) ? 0 : 1);
			PDef->meta = 1;
		} else {
			// debug( "Property %d exists.", *Pid );
			continue;
		}

		PDef->name = aaf_get_propertyValue (Prop, PID_MetaDefinition_Name, &AAFTypeID_String);

		if (!PDef->name) {
			warning ("Could not retrieve PID_MetaDefinition_Name property from PropertyDefinition.");
		}

		aafObject* TypeDefs = aaf_get_propertyValue (MetaDic, PID_MetaDictionary_TypeDefinitions, &AAFTypeID_TypeDefinitionStrongReferenceSet);

		if (!TypeDefs) {
			error ("Missing TypeDefinitions from MetaDictionary");
			return NULL;
		}

		aafWeakRef_t* WeakRefToType = aaf_get_propertyValue (Prop, PID_PropertyDefinition_Type, &AAFTypeID_PropertyDefinitionWeakReference);

		if (!WeakRefToType) {
			error ("Missing PID_PropertyDefinition_Type");
			return NULL;
		}

		aafObject* TypeDef = aaf_get_ObjectByWeakRef (TypeDefs, WeakRefToType);

		if (!TypeDef) {
			error ("Could not retrieve TypeDefinition from dictionary.");
			return NULL;
		}

		aafUID_t* typeUID = aaf_get_propertyValue (TypeDef, PID_MetaDefinition_Identification, &AAFTypeID_AUID);

		if (!typeUID) { /* req */
			error ("Missing PID_MetaDefinition_Identification");
			return NULL;
		}

		/*
		 * Looks like nobody cares about AAF standard TypeDefinition. All observed files
		 * had incorrect values for Type Name and Identification, even Avid's files. So...
		 */

		memcpy (&PDef->type, typeUID, sizeof (aafUID_t));

		// char *typeName = aaf_get_propertyValue( TypeDef, PID_MetaDefinition_Name, &AAFTypeID_String );
		//
		// debug( "TypeName :  %s (%s) |  name : %s.",
		// 	typeName,
		// 	aaft_TypeIDToText( typeUID ),
		// 	PDef->name );
		//
		// free( typeName );
	}

	return Class;
}

static aafObject*
newObject (AAF_Data* aafd, cfbNode* Node, aafClass* Class, aafObject* Parent)
{
	aafObject* Obj = calloc (1, sizeof (aafObject));

	if (!Obj) {
		error ("Out of memory");
		return NULL;
	}

	Obj->Name       = cfb_w16toUTF8 (Node->_ab, Node->_cb);
	Obj->aafd       = aafd;
	Obj->Class      = Class;
	Obj->Node       = Node;
	Obj->Properties = NULL;
	Obj->Parent     = Parent;
	Obj->Header     = NULL;
	Obj->Entry      = NULL;

	Obj->next     = NULL;
	Obj->prev     = NULL;
	Obj->nextObj  = aafd->Objects;
	aafd->Objects = Obj;

	return Obj;
}

static aafProperty*
newProperty (AAF_Data* aafd, aafPropertyDef* Def)
{
	aafProperty* Prop = calloc (1, sizeof (aafProperty));

	if (!Prop) {
		error ("Out of memory");
		return NULL;
	}

	Prop->pid = Def->pid;
	Prop->def = Def;

	return Prop;
}

static aafPropertyDef*
propertyIdExistsInClass (aafClass* Class, aafPID_t Pid)
{
	aafPropertyDef* PDef = NULL;

	foreachPropertyDefinition (PDef, Class->Properties) if (PDef->pid == Pid) return PDef;

	return NULL;
}

static int
setObjectStrongRefSet (aafObject* Obj, aafStrongRefSetHeader_t* Header, aafStrongRefSetEntry_t* Entry)
{
	AAF_Data* aafd = Obj->aafd;

	Obj->Header = malloc (sizeof (aafStrongRefSetHeader_t));

	if (!Obj->Header) {
		error ("Out of memory");
		return -1;
	}

	memcpy (Obj->Header, Header, sizeof (aafStrongRefSetHeader_t));

	/* Real entrySize, taking _identification into account. */
	uint32_t entrySize = sizeof (aafStrongRefSetEntry_t) + Header->_identificationSize;

	Obj->Entry = malloc (entrySize);

	if (!Obj->Entry) {
		error ("Out of memory");
		return -1;
	}

	memcpy (Obj->Entry, Entry, entrySize);

	return 0;
}

static int
setObjectStrongRefVector (aafObject* Obj, aafStrongRefVectorHeader_t* Header, aafStrongRefVectorEntry_t* Entry)
{
	/*
	 * aafStrongRefVectorHeader_t  and  aafStrongRefSetHeader_t begins with the same
	 * data bytes,  so  we  can  safely  memcpy to the first one from the second one,
	 * the remaining bytes simply remaining null.
	 * The same applies  to aafStrongRefVectorEntry_t and aafStrongRefVectorHeader_t.
	 */

	AAF_Data* aafd = Obj->aafd;

	Obj->Header = calloc (1, sizeof (aafStrongRefSetHeader_t));

	if (!Obj->Header) {
		error ("Out of memory");
		return -1;
	}

	memcpy (Obj->Header, Header, sizeof (aafStrongRefVectorHeader_t));

	Obj->Entry = calloc (1, sizeof (aafStrongRefSetEntry_t));

	if (!Obj->Entry) {
		error ("Out of memory");
		return -1;
	}

	memcpy (Obj->Entry, Entry, sizeof (aafStrongRefVectorEntry_t));

	return 0;
}

static int
retrieveStrongReference (AAF_Data* aafd, aafProperty* Prop, aafObject* Parent)
{
	/*
	 * Initial property value is a unicode string holding the name of a child node.
	 * This child node being the object referenced, we store that object directly
	 * as the property value, instead of the initial child node name.
	 */

	char* name = cfb_w16toUTF8 (Prop->val, Prop->len);

	free (Prop->val);
	Prop->val = NULL;

	cfbNode* Node = cfb_getChildNode (aafd->cfbd, name, Parent->Node);

	free (name);

	if (!Node) {
		error ("Could not find child node.");
		return -1;
	}

	aafClass* Class = aafclass_getClassByID (aafd, (aafUID_t*)&Node->_clsId);

	if (!Class) {
		error ("Could not retrieve Class %s @ \"%s\".",
		       aaft_ClassIDToText (aafd, (aafUID_t*)&Node->_clsId),
		       aaf_get_ObjectPath (Parent));
		return -1;
	}

	Prop->val = newObject (aafd, Node, Class, Parent);

	if (!Prop->val) {
		return -1;
	}

	int rc = retrieveObjectProperties (aafd, Prop->val);

	if (rc < 0) {
		return -1;
	}

	return 0;
}

static int
retrieveStrongReferenceSet (AAF_Data* aafd, aafProperty* Prop, aafObject* Parent)
{
	aafStrongRefSetHeader_t* Header = NULL;
	aafStrongRefSetEntry_t*  Entry  = NULL;

	char* refName = cfb_w16toUTF8 (Prop->val, Prop->len);

	free (Prop->val);
	Prop->val = NULL;

	cfbNode* Node = getStrongRefIndexNode (aafd, Parent, refName);

	if (!Node) {
		error ("Could not retrieve StrongReferenceSet's Index node.");
		goto err;
	}

	Header = getStrongRefSetList (aafd, Node, Parent);

	if (!Header) {
		error ("Could not retrieve StrongReferenceSet's CFB Stream.");
		goto err;
	}

	Entry = malloc (sizeof (aafStrongRefSetEntry_t) + Header->_identificationSize);

	if (!Entry) {
		error ("Out of memory");
		goto err;
	}

	memset (Entry, 0x00, sizeof (aafStrongRefSetEntry_t));

	uint32_t i  = 0;
	int      rc = 0;

	foreachStrongRefSetEntry (Header, (*Entry), i)
	{
		Node = getStrongRefEntryNode (aafd, Parent, refName, Entry->_localKey);

		if (!Node) {
			continue;
		}

		aafClass* Class = aafclass_getClassByID (aafd, (aafUID_t*)&Node->_clsId);

		if (!Class) {
			error ("Could not retrieve Class %s.",
			       aaft_ClassIDToText (aafd, (aafUID_t*)&Node->_clsId));
			continue;
		}

		aafObject* Obj = newObject (aafd, Node, Class, Parent);

		if (!Obj) {
			goto err;
		}

		rc = setObjectStrongRefSet (Obj, Header, Entry);

		if (rc < 0) {
			goto err;
		}

		rc = retrieveObjectProperties (aafd, Obj);

		if (rc < 0) {
			goto err;
		}

		Obj->next = Prop->val;
		Prop->val = Obj;
	}

	rc = 0;
	goto end;

err:
	rc = -1;

end:

	free (refName);
	free (Header);
	free (Entry);

	return rc;
}

static int
retrieveStrongReferenceVector (AAF_Data* aafd, aafProperty* Prop, aafObject* Parent)
{
	int        rc           = 0;
	aafByte_t* vectorStream = NULL;

	char* refName = cfb_w16toUTF8 (Prop->val, Prop->len);

	free (Prop->val);
	Prop->val = NULL;

	cfbNode* Node = getStrongRefIndexNode (aafd, Parent, refName);

	if (!Node) {
		goto err;
	}

	vectorStream = getStrongRefVectorList (aafd, Node, Parent);

	if (!vectorStream) {
		error ("Could not retrieve StrongRefVectorList");
		goto err;
	}

	aafStrongRefVectorHeader_t Header;
	aafStrongRefVectorEntry_t  Entry;

	memcpy (&Header, vectorStream, sizeof (aafStrongRefVectorHeader_t));

	uint32_t i = 0;

	foreachStrongRefVectorEntry (vectorStream, Header, Entry, i)
	{
		Node = getStrongRefEntryNode (aafd, Parent, refName, Entry._localKey);

		if (!Node) {
			continue;
		}

		aafClass* Class = aafclass_getClassByID (aafd, (aafUID_t*)&Node->_clsId);

		if (!Class) {
			warning ("Could not retrieve Class ID %s.",
			         aaft_ClassIDToText (aafd, (aafUID_t*)&Node->_clsId));
			continue;
		}

		aafObject* Obj = newObject (aafd, Node, Class, Parent);

		if (!Obj) {
			goto err;
		}

		rc = setObjectStrongRefVector (Obj, &Header, &Entry);

		if (rc < 0) {
			goto err;
		}

		rc = retrieveObjectProperties (aafd, Obj);

		if (rc < 0) {
			goto err;
		}

		/*
		 * Vectors are ordered.
		 */

		if (Prop->val != NULL) {
			aafObject* tmp = Prop->val;

			for (; tmp != NULL; tmp = tmp->next)
				if (!tmp->next)
					break;

			Obj->prev = tmp;
			tmp->next = Obj;
		} else {
			Obj->prev = NULL;
			Prop->val = Obj;
		}
	}

	rc = 0;
	goto end;

err:
	rc = -1;

end:
	free (refName);
	free (vectorStream);

	return rc;
}

static int
retrieveProperty (AAF_Data* aafd, aafObject* Obj, aafPropertyDef* Def, aafPropertyIndexEntry_t* p, aafByte_t* v, uint8_t bo)
{
	(void)bo; // TODO: ByteOrder support ?

	aafProperty* Prop = newProperty (aafd, Def);

	if (!Prop) {
		return -1;
	}

	Prop->sf = p->_storedForm;

	/*
	        TODO Prop->len / Prop->val ---> retrieveStrongReference() retrieveStrongReferenceSet() retrieveStrongReferenceVector()
	        only used to retrieve node name ? There could be a better approach.
	 */

	Prop->len = p->_length;

	Prop->val = malloc (p->_length);

	if (!Prop->val) {
		error ("Out of memory");
		free (Prop);
		return -1;
	}

	memcpy (Prop->val, v, p->_length);

	Prop->next      = Obj->Properties;
	Obj->Properties = Prop;

	switch (p->_storedForm) {
		case SF_STRONG_OBJECT_REFERENCE:
			return retrieveStrongReference (aafd, Prop, Obj);

		case SF_STRONG_OBJECT_REFERENCE_SET:
			return retrieveStrongReferenceSet (aafd, Prop, Obj);

		case SF_STRONG_OBJECT_REFERENCE_VECTOR:
			return retrieveStrongReferenceVector (aafd, Prop, Obj);

		default:
			break;
	}

	return 0;
}

static int
retrieveObjectProperties (AAF_Data* aafd, aafObject* Obj)
{
	int rc = 0;

	aafByte_t* propStream = getNodeProperties (aafd, Obj->Node);

	if (!propStream) {
		error ("Could not retrieve object %s properties : %s",
		       aaft_ClassIDToText (aafd, Obj->Class->ID),
		       aaf_get_ObjectPath (Obj));
		goto err;
	}

	aafPropertyIndexHeader_t Header;
	aafPropertyIndexEntry_t  Prop;

	memcpy (&Header, propStream, sizeof (aafPropertyIndexHeader_t));

	aafByte_t*      value = NULL;
	aafPropertyDef* PDef  = NULL;

	size_t valueOffset = 0;

	uint32_t i = 0;

	foreachPropertyEntry (propStream, Header, Prop, value, valueOffset, i)
	{
		PDef = aafclass_getPropertyDefinitionByID (Obj->Class, Prop._pid);

		if (!PDef) {
			warning ("Unknown property 0x%04x (%s) of object %s",
			         Prop._pid,
			         aaft_PIDToText (aafd, Prop._pid),
			         aaft_ClassIDToText (aafd, Obj->Class->ID));
			continue;
		}

		rc = retrieveProperty (aafd, Obj, PDef, &Prop, value, Header._byteOrder);

		if (rc < 0) {
			error ("Could not retrieve property %s of object %s",
			       aaft_PIDToText (aafd, PDef->pid),
			       aaft_ClassIDToText (aafd, Obj->Class->ID));
			goto err;
		}
	}

	rc = 0;
	goto end;

err:
	rc = -1;

end:

	free (propStream);

	return rc;
}

static cfbNode*
getStrongRefIndexNode (AAF_Data* aafd, aafObject* Parent, const char* refName)
{
	char name[CFB_NODE_NAME_SZ];

	int rc = snprintf (name, CFB_NODE_NAME_SZ, "%s index", refName);

	if (rc < 0 || (size_t)rc >= CFB_NODE_NAME_SZ) {
		error ("snprintf() error");
		return NULL;
	}

	cfbNode* Node = cfb_getChildNode (aafd->cfbd, name, Parent->Node);

	if (!Node) {
		error ("Could not retrieve Reference Set/Vector Index Node @ \"%s/%s index\"",
		       aaf_get_ObjectPath (Parent),
		       refName);
		return NULL;
	}

	return Node;
}

static cfbNode*
getStrongRefEntryNode (AAF_Data* aafd, aafObject* Parent, const char* refName, uint32_t index)
{
	char name[CFB_NODE_NAME_SZ];

	int rc = snprintf (name, CFB_NODE_NAME_SZ, "%s{%x}", refName, index);

	if (rc < 0 || (size_t)rc >= CFB_NODE_NAME_SZ) {
		error ("snprintf() error");
		return NULL;
	}

	cfbNode* Node = cfb_getChildNode (aafd->cfbd, name, Parent->Node);

	if (!Node) {
		error ("Could not retrieve Reference Set/vector Entry Node @ \"%s/%s index\"",
		       aaf_get_ObjectPath (Parent),
		       refName);
		return NULL;
	}

	return Node;
}

static aafByte_t*
getNodeProperties (AAF_Data* aafd, cfbNode* Node)
{
	if (!Node) {
		error ("Node is NULL");
		return NULL;
	}

	uint64_t   stream_sz = 0;
	aafByte_t* stream    = NULL;

	cfbNode* propNode = cfb_getChildNode (aafd->cfbd, "properties", Node);

	if (!propNode) {
		error ("Could not retrieve Property Node");
		return NULL;
	}

	cfb_getStream (aafd->cfbd, propNode, &stream, &stream_sz);

	if (!stream) {
		error ("Could not retrieve Property Stream");
		return NULL;
	}

	/*
	 *  Ensures PropHeader + all PropEntries + all PropValues matches the Stream size.
	 *  TODO : is the following test important ?
	 */

	/*
	        uint32_t prop_sz = sizeof(aafPropertyIndexHeader_t);

	        uint32_t i = 0;

	        for ( i = 0; i < ((aafPropertyIndexHeader_t*)stream)->_entryCount; i++ )
	                prop_sz += (((aafPropertyIndexEntry_t*)(stream+((sizeof(aafPropertyIndexEntry_t)*i)+sizeof(aafPropertyIndexHeader_t))))->_length) + sizeof(aafPropertyIndexEntry_t);

	        if ( prop_sz != stream_sz )
	                warning( L"Stream length (%lu Bytes) does not match property length (%u Bytes).",
	                        stream_sz,
	                        prop_sz );
	*/

	return stream;
}

static aafStrongRefSetHeader_t*
getStrongRefSetList (AAF_Data* aafd, cfbNode* Node, aafObject* Parent)
{
	if (!Node)
		return NULL;

	aafByte_t* stream    = NULL;
	uint64_t   stream_sz = 0;

	cfb_getStream (aafd->cfbd, Node, &stream, &stream_sz);

	if (!stream) {
		char* refName = cfb_w16toUTF8 (Node->_ab, Node->_cb);

		error ("Could not retrieve StrongReferenceSet Index Stream @ \"%s/%s index\"",
		       aaf_get_ObjectPath (Parent),
		       refName);

		free (refName);

		return NULL;
	}

	return (aafStrongRefSetHeader_t*)stream;
}

static aafByte_t*
getStrongRefVectorList (AAF_Data* aafd, cfbNode* Node, aafObject* Parent)
{
	if (!Node)
		return NULL;

	aafByte_t* stream    = NULL;
	uint64_t   stream_sz = 0;

	cfb_getStream (aafd->cfbd, Node, &stream, &stream_sz);

	if (!stream) {
		char* refName = cfb_w16toUTF8 (Node->_ab, Node->_cb);

		error ("Could not retrieve StrongReferenceVector Index Stream \"%s/%s index\"",
		       aaf_get_ObjectPath (Parent),
		       refName);

		return NULL;
	}

	return stream;
}
