/*
 * 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.
 */

/**
 * @file LibAAF/AAFIface/AAFIface.c
 * @brief AAF processing
 * @author Adrien Gesta-Fline
 * @version 0.1
 * @date 04 october 2017
 *
 * @ingroup AAFIface
 * @addtogroup AAFIface
 *
 * The AAFIface provides the actual processing of the AAF Objects in order to show
 * essences and clips in a simplified manner. Indeed, AAF has many different ways to
 * store data and metadata. Thus, the AAFIface is an abstraction layer that provides
 * a constant and unique representation method of essences and clips.
 *
 *
 *
 * @{
 */

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

#include "aaf/AAFIParser.h"
#include "aaf/AAFIface.h"
#include "aaf/log.h"
#include "aaf/utils.h"

#define debug(...) \
	AAF_LOG (aafi->log, aafi, LOG_SRC_ID_AAF_IFACE, VERB_DEBUG, __VA_ARGS__)

#define warning(...) \
	AAF_LOG (aafi->log, aafi, LOG_SRC_ID_AAF_IFACE, VERB_WARNING, __VA_ARGS__)

#define error(...) \
	AAF_LOG (aafi->log, aafi, LOG_SRC_ID_AAF_IFACE, VERB_ERROR, __VA_ARGS__)

AAF_Iface*
aafi_alloc (AAF_Data* aafd)
{
	AAF_Iface* aafi = calloc (1, sizeof (AAF_Iface));

	if (!aafi) {
		return NULL;
	}

	aafi->log = laaf_new_log ();

	if (!aafi->log) {
		goto err;
	}

	aafi->Audio = calloc (1, sizeof (aafiAudio));

	if (!aafi->Audio) {
		goto err;
	}

	aafi->Video = calloc (1, sizeof (aafiVideo));

	if (!aafi->Video) {
		goto err;
	}

	if (aafd) {
		aafi->aafd = aafd;
	} else {
		aafi->aafd = aaf_alloc (aafi->log);

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

	return aafi;

err:
	aafi_release (&aafi);

	return NULL;
}

void
aafi_set_debug (AAF_Iface* aafi, enum verbosityLevel_e verb, int ansicolor, FILE* fp, void (*callback) (struct aafLog* log, void* ctxdata, int lib, int type, const char* srcfile, const char* srcfunc, int lineno, const char* msg, void* user), void* user)
{
	if (!aafi) {
		return;
	}

	aafi->log->verb      = verb;
	aafi->log->ansicolor = ansicolor;
	aafi->log->fp        = fp;

	if (callback) {
		aafi->log->log_callback = callback;
	}

	if (user) {
		aafi->log->user = user;
	}

#ifdef _WIN32
	/* we dont want junk bytes to be printed to a windows file */
	if (fp != stdout && fp != stderr) {
		aafi->log->ansicolor = 0;
	}
#endif
}

int
aafi_set_option_int (AAF_Iface* aafi, const char* optname, int val)
{
	if (strcmp (optname, "trace") == 0) {
		aafi->ctx.options.trace = val;
		return 0;
	} else if (strcmp (optname, "dump_meta") == 0) {
		aafi->ctx.options.dump_meta = val;
		return 0;
	} else if (strcmp (optname, "dump_tagged_value") == 0) {
		aafi->ctx.options.dump_tagged_value = val;
		return 0;
	} else if (strcmp (optname, "protools") == 0) {
		aafi->ctx.options.protools = val;
		return 0;
	} else if (strcmp (optname, "mobid_essence_filename") == 0) {
		aafi->ctx.options.mobid_essence_filename = val;
		return 0;
	}

	return 1;
}

int
aafi_set_option_str (AAF_Iface* aafi, const char* optname, const char* val)
{
	if (strcmp (optname, "media_location") == 0) {
		free (aafi->ctx.options.media_location);
		aafi->ctx.options.media_location = laaf_util_c99strdup (val);

		if (val && !aafi->ctx.options.media_location) {
			return -1;
		}

		return 0;
	} else if (strcmp (optname, "dump_class_aaf_properties") == 0) {
		free (aafi->ctx.options.dump_class_aaf_properties);
		aafi->ctx.options.dump_class_aaf_properties = laaf_util_c99strdup (val);

		if (val && !aafi->ctx.options.dump_class_aaf_properties) {
			return -1;
		}

		return 0;
	} else if (strcmp (optname, "dump_class_raw_properties") == 0) {
		free (aafi->ctx.options.dump_class_raw_properties);
		aafi->ctx.options.dump_class_raw_properties = laaf_util_c99strdup (val);

		if (val && !aafi->ctx.options.dump_class_raw_properties) {
			return -1;
		}

		return 0;
	}

	return 1;
}

int
aafi_load_file (AAF_Iface* aafi, const char* file)
{
	if (!aafi || !file || aaf_load_file (aafi->aafd, file)) {
		return 1;
	}

	aafi_retrieveData (aafi);

	return 0;
}

void
aafi_release (AAF_Iface** aafi)
{
	if (!aafi || !(*aafi)) {
		return;
	}

	aaf_release (&(*aafi)->aafd);

	if ((*aafi)->Audio != NULL) {
		aafi_freeAudioTracks (&(*aafi)->Audio->Tracks);
		aafi_freeAudioEssences (&(*aafi)->Audio->essenceFiles);

		aafiAudioEssencePointer* essencePointer = (*aafi)->Audio->essencePointerList;

		while (essencePointer) {
			essencePointer = aafi_freeAudioEssencePointer (essencePointer);
		}

		free ((*aafi)->Audio);
	}

	if ((*aafi)->Video != NULL) {
		aafi_freeVideoTracks (&(*aafi)->Video->Tracks);
		aafi_freeVideoEssences (&(*aafi)->Video->essenceFiles);

		free ((*aafi)->Video);
	}

	aafi_freeMarkers (&(*aafi)->Markers);
	aafi_freeMetadata (&((*aafi)->metadata));

	free ((*aafi)->compositionName);

	free ((*aafi)->ctx.options.dump_class_aaf_properties);
	free ((*aafi)->ctx.options.dump_class_raw_properties);
	free ((*aafi)->ctx.options.media_location);
	free ((*aafi)->Timecode);

	laaf_free_log ((*aafi)->log);

	free (*aafi);

	*aafi = NULL;
}

aafiAudioClip*
aafi_timelineItemToAudioClip (aafiTimelineItem* audioItem)
{
	if (!audioItem || audioItem->type != AAFI_AUDIO_CLIP) {
		return NULL;
	}

	return audioItem->data;
}

aafiTransition*
aafi_timelineItemToCrossFade (aafiTimelineItem* audioItem)
{
	if (!audioItem || audioItem->type != AAFI_TRANS) {
		return NULL;
	}

	aafiTransition* Trans = audioItem->data;

	if (!Trans || !(Trans->flags & AAFI_TRANS_XFADE))
		return NULL;

	return Trans;
}

aafiTransition*
aafi_getFadeIn (aafiAudioClip* audioClip)
{
	if (!audioClip) {
		return NULL;
	}

	aafiTimelineItem* audioItem = audioClip->timelineItem;

	if (!audioItem) {
		return NULL;
	}

	if (audioItem->prev != NULL &&
	    audioItem->prev->type == AAFI_TRANS) {
		aafiTransition* Trans = audioItem->prev->data;

		if (Trans->flags & AAFI_TRANS_FADE_IN)
			return Trans;
	}

	return NULL;
}

aafiTransition*
aafi_getFadeOut (aafiAudioClip* audioClip)
{
	if (!audioClip) {
		return NULL;
	}

	aafiTimelineItem* audioItem = audioClip->timelineItem;

	if (!audioItem) {
		return NULL;
	}

	if (audioItem->next != NULL &&
	    audioItem->next->type == AAFI_TRANS) {
		aafiTransition* Trans = audioItem->next->data;

		if (Trans->flags & AAFI_TRANS_FADE_OUT)
			return Trans;
	}

	return NULL;
}

int
aafi_get_clipIndex (aafiAudioClip* audioClip)
{
	if (!audioClip) {
		return 0;
	}

	int                      index        = 0;
	struct aafiTimelineItem* timelineItem = NULL;
	struct aafiAudioTrack*   track        = audioClip->track;

	AAFI_foreachTrackItem (track, timelineItem)
	{
		if (timelineItem->type == AAFI_AUDIO_CLIP) {
			index++;
		}
		if (timelineItem->data == audioClip) {
			return index;
		}
	}

	return 0;
}

aafPosition_t
aafi_convertUnit (aafPosition_t value, aafRational_t* valueEditRate, aafRational_t* destEditRate)
{
	if (!valueEditRate || !destEditRate) {
		return value;
	}

	if (valueEditRate->numerator == destEditRate->numerator &&
	    valueEditRate->denominator == destEditRate->denominator) {
		/* same rate, no conversion */
		return value;
	}

	double valueEditRateFloat = ((valueEditRate->denominator == 0) ? 0.0 : ((float)valueEditRate->numerator / (float)valueEditRate->denominator));
	double destEditRateFloat  = ((destEditRate->denominator == 0) ? 0.0 : ((float)destEditRate->numerator / (float)destEditRate->denominator));

	if (valueEditRateFloat == 0) {
		return 0;
	}

	return (aafPosition_t)((double)value * (destEditRateFloat / valueEditRateFloat));
}

uint64_t
aafi_convertUnitUint64 (aafPosition_t value, aafRational_t* valueEditRate, aafRational_t* destEditRate)
{
	if (!valueEditRate || !destEditRate) {
		if (value < 0) {
			/* TODO is ULONG_MAX ok for max uint64_t ? */
			return ULONG_MAX;
		}

		return (uint64_t)value;
	}

	if (valueEditRate->numerator == destEditRate->numerator &&
	    valueEditRate->denominator == destEditRate->denominator) {
		/* same rate, no conversion */
		if (value < 0) {
			/* TODO is ULONG_MAX ok for max uint64_t ? */
			return ULONG_MAX;
		}

		return (uint64_t)value;
	}

	double valueEditRateFloat = ((valueEditRate->denominator == 0) ? 0.0 : ((float)valueEditRate->numerator / (float)valueEditRate->denominator));
	double destEditRateFloat  = ((destEditRate->denominator == 0) ? 0.0 : ((float)destEditRate->numerator / (float)destEditRate->denominator));

	if (valueEditRateFloat == 0) {
		return 0;
	}

	return (uint64_t)((double)value * (destEditRateFloat / valueEditRateFloat));
}

int
aafi_removeTimelineItem (AAF_Iface* aafi, aafiTimelineItem* timelineItem)
{
	if (!timelineItem) {
		return 0;
	}

	if (timelineItem->prev != NULL) {
		timelineItem->prev->next = timelineItem->next;
	}

	if (timelineItem->next != NULL) {
		timelineItem->next->prev = timelineItem->prev;
	}

	aafiAudioTrack* audioTrack = NULL;

	AAFI_foreachAudioTrack (aafi, audioTrack)
	{
		if (audioTrack->timelineItems == timelineItem) {
			audioTrack->timelineItems = timelineItem->next;
		}
	}

	aafi_freeTimelineItem (timelineItem);

	return 0;
}

int
aafi_getAudioEssencePointerChannelCount (aafiAudioEssencePointer* essencePointerList)
{
	/*
	 * If essencePointerList holds a single multichannel essence file and if
	 * essencePointer->essenceChannel is set, then clip is mono and audio comes
	 * from essencePointer->essenceChannel of essencePointer.essenceFile.
	 *
	 * If essencePointerList holds a single multichannel essence file and if
	 * essencePointer->essenceChannel is null, then clip is multichannel and
	 * clip channel count equals essence->channels.
	 *
	 * If essencePointerList holds multiple pointers to multiple essence files,
	 * then each file should be mono and describe a clip channel. Thus, clip
	 * channel count equals pointers count.
	 */

	// if ( !essencePointerList ) {
	// 	return 0;
	// }

	int                      essencePointerCount = 0;
	aafiAudioEssencePointer* essencePointer      = NULL;

	AAFI_foreachEssencePointer (essencePointerList, essencePointer)
	{
		essencePointerCount++;
	}

	return (essencePointerCount > 1) ? essencePointerCount : (essencePointerList->essenceChannel) ? 1
	                                                                                              : essencePointerList->essenceFile->channels;
}

int
aafi_applyGainOffset (AAF_Iface* aafi, aafiAudioGain** gain, aafiAudioGain* offset)
{
	if ((offset->flags & AAFI_AUDIO_GAIN_MASK) & AAFI_AUDIO_GAIN_VARIABLE) {
		debug ("Variable gain offset is not supported");
		return -1;
	}

	if (*gain == NULL) {
		/*
		 * apply offset as new gain
		 */

		debug ("Applying gain to clip as a new gain");

		(*gain) = aafi_newAudioGain (aafi, offset->flags & AAFI_AUDIO_GAIN_MASK, offset->flags & AAFI_INTERPOL_MASK, NULL);

		(*gain)->time  = calloc ((uint64_t)offset->pts_cnt, sizeof (aafRational_t));
		(*gain)->value = calloc ((uint64_t)offset->pts_cnt, sizeof (aafRational_t));

		if (!(*gain)->time || !(*gain)->value) {
			error ("Out of memory");
			aafi_freeAudioGain (*gain);
			return -1;
		}

		for (unsigned int i = 0; i < (*gain)->pts_cnt; i++) {
			(*gain)->value[i].numerator   = offset->value[0].numerator;
			(*gain)->value[i].denominator = offset->value[0].denominator;
			// debug( "Setting (*gain)->value[%i] = %i/%i",
			// 	i,
			// 	(*gain)->value[i].numerator,
			// 	(*gain)->value[i].denominator );
		}
	} else {
		/*
		 * update existing constant or variable gain
		 */

		debug ("Applying gain to clip: %i/%i (%+05.1lf dB) ",
		       (*gain)->value[0].numerator,
		       (*gain)->value[0].denominator,
		       20 * log10 (aafRationalToDouble ((*gain)->value[0])));

		for (unsigned int i = 0; i < (*gain)->pts_cnt; i++) {
			/*
			 * most of the time, gain values are made of very high numbers and denominator
			 * is the same accross all gains in file. Thus, we devide both gain numbers
			 * by offset denominator, so we fit inside uint32_t.
			 */
			(*gain)->value[i].numerator   = (int32_t)(((int64_t)(*gain)->value[i].numerator * (int64_t)offset->value[0].numerator) / (int64_t)offset->value[0].denominator);
			(*gain)->value[i].denominator = (int32_t)(((int64_t)(*gain)->value[i].denominator * (int64_t)offset->value[0].denominator) / (int64_t)offset->value[0].denominator);
			// debug( "Setting (*gain)->value[%i] = %i/%i * %i/%i",
			// 	i,
			// 	(*gain)->value[i].numerator,
			// 	(*gain)->value[i].denominator,
			// 	offset->value[0].numerator,
			// 	offset->value[0].denominator );
		}
	}

	return 0;
}

aafiAudioTrack*
aafi_newAudioTrack (AAF_Iface* aafi)
{
	aafiAudioTrack* track = calloc (1, sizeof (aafiAudioTrack));

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

	track->Audio  = aafi->Audio;
	track->format = AAFI_TRACK_FORMAT_NOT_SET;
	track->next   = NULL;

	/* Add to track list */

	if (aafi->Audio->Tracks != NULL) {
		aafiAudioTrack* tmp = aafi->Audio->Tracks;

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

		tmp->next = track;
	} else {
		aafi->Audio->Tracks = track;
	}

	return track;
}

aafiVideoTrack*
aafi_newVideoTrack (AAF_Iface* aafi)
{
	aafiVideoTrack* track = calloc (1, sizeof (aafiVideoTrack));

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

	track->Video = aafi->Video;
	track->next  = NULL;

	/* Add to track list */

	if (aafi->Video->Tracks != NULL) {
		aafiVideoTrack* tmp = aafi->Video->Tracks;

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

		tmp->next = track;
	} else {
		aafi->Video->Tracks = track;
	}

	return track;
}

aafiTimelineItem*
aafi_newTimelineItem (AAF_Iface* aafi, void* track, int itemType, void* data)
{
	aafiTimelineItem* timelineItem = calloc (1, sizeof (aafiTimelineItem));

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

	timelineItem->type = itemType;
	timelineItem->data = data;

	if (itemType == AAFI_AUDIO_CLIP || itemType == AAFI_TRANS) {
		if (track != NULL) {
			/* Add to track's timelineItem list */

			if (((aafiAudioTrack*)track)->timelineItems != NULL) {
				aafiTimelineItem* tmp = ((aafiAudioTrack*)track)->timelineItems;

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

				tmp->next          = timelineItem;
				timelineItem->prev = tmp;
			} else {
				((aafiAudioTrack*)track)->timelineItems = timelineItem;
				timelineItem->prev                      = NULL;
			}
		}
	} else if (itemType == AAFI_VIDEO_CLIP) {
		if (track != NULL) {
			/* Add to track's timelineItem list */

			if (((aafiVideoTrack*)track)->timelineItems != NULL) {
				aafiTimelineItem* tmp = ((aafiVideoTrack*)track)->timelineItems;

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

				tmp->next          = timelineItem;
				timelineItem->prev = tmp;
			} else {
				((aafiVideoTrack*)track)->timelineItems = timelineItem;
				timelineItem->prev                      = NULL;
			}
		}
	}

	return timelineItem;
}

aafiAudioClip*
aafi_newAudioClip (AAF_Iface* aafi, aafiAudioTrack* track)
{
	aafiAudioClip* audioClip = calloc (1, sizeof (aafiAudioClip));

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

	audioClip->track        = track;
	audioClip->timelineItem = aafi_newTimelineItem (aafi, track, AAFI_AUDIO_CLIP, audioClip);

	if (!audioClip->timelineItem) {
		error ("Could not create new timelineItem");
		free (audioClip);
		return NULL;
	}

	return audioClip;
}

aafiVideoClip*
aafi_newVideoClip (AAF_Iface* aafi, aafiVideoTrack* track)
{
	aafiVideoClip* videoClip = calloc (1, sizeof (aafiVideoClip));

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

	videoClip->track        = track;
	videoClip->timelineItem = aafi_newTimelineItem (aafi, track, AAFI_VIDEO_CLIP, videoClip);

	if (!videoClip->timelineItem) {
		error ("Could not create new timelineItem");
		free (videoClip);
		return NULL;
	}

	return videoClip;
}

aafiTransition*
aafi_newTransition (AAF_Iface* aafi, aafiAudioTrack* track)
{
	aafiTransition* trans = calloc (1, sizeof (aafiTransition));

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

	trans->timelineItem = aafi_newTimelineItem (aafi, track, AAFI_TRANS, trans);

	if (!trans->timelineItem) {
		error ("Could not create new timelineItem");
		free (trans);
		return NULL;
	}

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

	if (!trans->time_a || !trans->value_a) {
		error ("Out of memory");
		aafi_freeTimelineItem (trans->timelineItem);
		return NULL;
	}

	return trans;
}

aafiMarker*
aafi_newMarker (AAF_Iface* aafi, aafRational_t* editRate, aafPosition_t start, aafPosition_t length, char* name, char* comment, uint16_t*(RGBColor[]))
{
	aafiMarker* marker = calloc (sizeof (aafiMarker), 1);

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

	marker->edit_rate = editRate;
	marker->start     = start;
	marker->length    = length;

	marker->name    = name;
	marker->comment = comment;

	marker->prev = NULL;
	marker->next = NULL;

	if (RGBColor && *RGBColor) {
		marker->RGBColor[0] = (*RGBColor)[0];
		marker->RGBColor[1] = (*RGBColor)[1];
		marker->RGBColor[2] = (*RGBColor)[2];
	}

	if (aafi->Markers != NULL) {
		aafiMarker* tmp = aafi->Markers;

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

		tmp->next    = marker;
		marker->prev = marker;
	} else {
		aafi->Markers = marker;
		marker->prev  = NULL;
	}

	return marker;
}

aafiMetaData*
aafi_newMetadata (AAF_Iface* aafi, aafiMetaData** CommentList)
{
	if (!CommentList) {
		return NULL;
	}

	aafiMetaData* UserComment = calloc (1, sizeof (aafiMetaData));

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

	if (*CommentList != NULL) {
		UserComment->next = *CommentList;
		*CommentList      = UserComment;
	} else {
		*CommentList = UserComment;
	}

	return UserComment;
}

aafiAudioEssencePointer*
aafi_newAudioEssencePointer (AAF_Iface* aafi, aafiAudioEssencePointer** list, aafiAudioEssenceFile* audioEssenceFile, uint32_t* essenceChannelNum)
{
	aafiAudioEssencePointer* essencePointer = calloc (1, sizeof (aafiAudioEssencePointer));

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

	essencePointer->aafi           = aafi;
	essencePointer->essenceFile    = audioEssenceFile;
	essencePointer->essenceChannel = (essenceChannelNum) ? *essenceChannelNum : 0;

	if (*list) {
		aafiAudioEssencePointer* last = *list;
		while (last->next != NULL) {
			last = last->next;
		}
		last->next = essencePointer;
	} else {
		*list = essencePointer;
	}

	essencePointer->aafiNext        = aafi->Audio->essencePointerList;
	aafi->Audio->essencePointerList = essencePointer;

	return *list;
}

aafiAudioEssenceFile*
aafi_newAudioEssence (AAF_Iface* aafi)
{
	aafiAudioEssenceFile* audioEssenceFile = calloc (1, sizeof (aafiAudioEssenceFile));

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

	audioEssenceFile->samplerateRational = malloc (sizeof (aafRational_t));

	if (!audioEssenceFile->samplerateRational) {
		error ("Out of memory");
		goto err;
	}

	audioEssenceFile->samplerateRational->numerator   = 1;
	audioEssenceFile->samplerateRational->denominator = 1;

	audioEssenceFile->next = aafi->Audio->essenceFiles;

	aafi->Audio->essenceFiles = audioEssenceFile;
	aafi->Audio->essenceCount++;

	return audioEssenceFile;

err:
	if (audioEssenceFile) {
		free (audioEssenceFile->samplerateRational);
		free (audioEssenceFile);
	}

	return NULL;
}

aafiVideoEssence*
aafi_newVideoEssence (AAF_Iface* aafi)
{
	aafiVideoEssence* videoEssenceFile = calloc (1, sizeof (aafiVideoEssence));

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

	videoEssenceFile->next = aafi->Video->essenceFiles;

	aafi->Video->essenceFiles = videoEssenceFile;

	return videoEssenceFile;
}

aafiAudioGain*
aafi_newAudioGain (AAF_Iface* aafi, enum aafiAudioGain_e type, enum aafiInterpolation_e interpol, aafRational_t* singleValue)
{
	aafiAudioGain* Gain = calloc (1, sizeof (aafiAudioGain));

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

	Gain->flags |= type;
	Gain->flags |= interpol;

	if (singleValue) {
		Gain->pts_cnt = 1;
		Gain->value   = calloc (1, sizeof (aafRational_t));

		if (!Gain->value) {
			error ("Out of memory");
			free (Gain);
			return NULL;
		}

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

	return Gain;
}

aafiAudioGain*
aafi_newAudioPan (AAF_Iface* aafi, enum aafiAudioGain_e type, enum aafiInterpolation_e interpol, aafRational_t* singleValue)
{
	return aafi_newAudioGain (aafi, type, interpol, singleValue);
}

void
aafi_freeAudioTracks (aafiAudioTrack** tracks)
{
	if (!tracks || !(*tracks)) {
		return;
	}

	aafiAudioTrack* track     = NULL;
	aafiAudioTrack* nextTrack = NULL;

	for (track = (*tracks); track != NULL; track = nextTrack) {
		nextTrack = track->next;

		free (track->name);
		aafi_freeAudioGain (track->gain);
		aafi_freeAudioPan (track->pan);
		aafi_freeTimelineItems (&track->timelineItems);

		free (track);
	}

	*tracks = NULL;
}

void
aafi_freeVideoTracks (aafiVideoTrack** tracks)
{
	if (*(tracks) == NULL) {
		return;
	}

	aafiVideoTrack* track     = NULL;
	aafiVideoTrack* nextTrack = NULL;

	for (track = (*tracks); track != NULL; track = nextTrack) {
		nextTrack = track->next;

		free (track->name);
		aafi_freeTimelineItems (&track->timelineItems);

		free (track);
	}

	*tracks = NULL;
}

void
aafi_freeTimelineItems (aafiTimelineItem** timelineItems)
{
	aafiTimelineItem* timelineItem = NULL;
	aafiTimelineItem* nextItem     = NULL;

	for (timelineItem = (*timelineItems); timelineItem != NULL; timelineItem = nextItem) {
		nextItem = timelineItem->next;
		aafi_freeTimelineItem (timelineItem);
	}

	*timelineItems = NULL;
}

void
aafi_freeTimelineItem (aafiTimelineItem* timelineItem)
{
	if (!timelineItem) {
		return;
	}

	if (timelineItem->type == AAFI_TRANS) {
		aafi_freeTransition (timelineItem->data);
	} else if (timelineItem->type == AAFI_AUDIO_CLIP) {
		aafi_freeAudioClip (timelineItem->data);
	} else if (timelineItem->type == AAFI_VIDEO_CLIP) {
		free (timelineItem->data);
	}

	free (timelineItem);
}

void
aafi_freeAudioClip (aafiAudioClip* audioClip)
{
	if (!audioClip) {
		return;
	}

	free (audioClip->subClipName);

	aafi_freeAudioGain (audioClip->gain);
	aafi_freeAudioGain (audioClip->automation);
	aafi_freeMetadata (&(audioClip->metadata));

	free (audioClip);
}

void
aafi_freeTransition (aafiTransition* Transition)
{
	if (!Transition) {
		return;
	}

	free (Transition->value_a);
	free (Transition->value_b);
	free (Transition->time_a);
	free (Transition->time_b);

	free (Transition);
}

void
aafi_freeMarkers (aafiMarker** Markers)
{
	aafiMarker* marker     = NULL;
	aafiMarker* nextMarker = NULL;

	for (marker = (*Markers); marker != NULL; marker = nextMarker) {
		nextMarker = marker->next;

		free (marker->name);
		free (marker->comment);

		free (marker);
	}

	*Markers = NULL;
}

void
aafi_freeMetadata (aafiMetaData** CommentList)
{
	aafiMetaData* UserComment = *CommentList;
	aafiMetaData* tmp         = NULL;

	while (UserComment != NULL) {
		tmp         = UserComment;
		UserComment = UserComment->next;

		free (tmp->name);
		free (tmp->text);

		free (tmp);
	}

	*CommentList = NULL;
}

aafiAudioEssencePointer*
aafi_freeAudioEssencePointer (aafiAudioEssencePointer* essencePointer)
{
	aafiAudioEssencePointer* next = essencePointer->aafiNext;

	free (essencePointer);

	return next;
}

void
aafi_freeAudioEssences (aafiAudioEssenceFile** audioEssenceFile)
{
	if (*(audioEssenceFile) == NULL) {
		return;
	}

	aafiAudioEssenceFile* nextAudioEssence = NULL;

	for (; (*audioEssenceFile) != NULL; *audioEssenceFile = nextAudioEssence) {
		nextAudioEssence = (*audioEssenceFile)->next;

		free ((*audioEssenceFile)->original_file_path);
		free ((*audioEssenceFile)->usable_file_path);
		free ((*audioEssenceFile)->name);
		free ((*audioEssenceFile)->unique_name);
		free ((*audioEssenceFile)->samplerateRational);

		aafi_freeMetadata (&((*audioEssenceFile)->metadata));

		free (*audioEssenceFile);
	}

	*audioEssenceFile = NULL;
}

void
aafi_freeVideoEssences (aafiVideoEssence** videoEssenceFile)
{
	if (*(videoEssenceFile) == NULL) {
		return;
	}

	aafiVideoEssence* nextVideoEssence = NULL;

	for (; (*videoEssenceFile) != NULL; *videoEssenceFile = nextVideoEssence) {
		nextVideoEssence = (*videoEssenceFile)->next;

		free ((*videoEssenceFile)->original_file_path);
		free ((*videoEssenceFile)->usable_file_path);
		free ((*videoEssenceFile)->name);
		free ((*videoEssenceFile)->unique_name);

		free (*videoEssenceFile);
	}

	*videoEssenceFile = NULL;
}

void
aafi_freeAudioGain (aafiAudioGain* gain)
{
	if (gain == NULL) {
		return;
	}

	free (gain->time);
	free (gain->value);

	free (gain);
}

void
aafi_freeAudioPan (aafiAudioPan* pan)
{
	aafi_freeAudioGain ((aafiAudioGain*)pan);
}

/**
 * @}
 */
