/*
 * 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/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 <stdio.h>
#include <stdlib.h>
#include <string.h>

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

#ifdef _WIN32
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
#endif

#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__)

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

	if (aafi == NULL) {
		return NULL;
	}

	aafi->dbg = laaf_new_debug ();

	if (aafi->dbg == NULL) {
		return NULL;
	}

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

	if (aafi->Audio == NULL) {
		return NULL;
	}

	aafi->Audio->Essences    = NULL;
	aafi->Audio->samplerate  = 0;
	aafi->Audio->samplesize  = 0;
	aafi->Audio->Tracks      = NULL;
	aafi->Audio->track_count = 0;
	aafi->Audio->length      = 0;

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

	if (aafi->Video == NULL) {
		return NULL;
	}

	aafi->Video->Essences = NULL;
	aafi->Video->Tracks   = NULL;
	aafi->Video->length   = 0;

	if (aafd != NULL) {
		aafi->aafd = aafd;
	} else {
		aafi->aafd = aaf_alloc (aafi->dbg);
	}

	aafi->Markers = NULL;

	aafi->compositionName = NULL;

	aafi->ctx.is_inside_derivation_chain        = 0;
	aafi->ctx.options.forbid_nonlatin_filenames = 0;
	aafi->ctx.options.trace                     = 0;

	return aafi;
}

void
aafi_enable_windows_VT100_output (void)
{
#ifdef _WIN32
	/* enables ANSI colors and unicode chars */
	HANDLE hOut   = GetStdHandle (STD_OUTPUT_HANDLE);
	DWORD  dwMode = 0;
	GetConsoleMode (hOut, &dwMode);
	SetConsoleMode (hOut, (dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING));
#endif
}

void
aafi_set_debug (AAF_Iface* aafi, verbosityLevel_e v, FILE* fp, void (*callback) (struct dbg* dbg, void* ctxdata, int lib, int type, const char* srcfile, const char* srcfunc, int lineno, const char* msg, void* user), void* user)
{
	aafi->dbg->verb = v;
	aafi->dbg->fp   = fp;

	if (callback) {
		aafi->dbg->debug_callback = callback;
	}

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

int
aafi_set_media_location (AAF_Iface* aafi, const char* path)
{
	if (aafi->ctx.options.media_location) {
		free (aafi->ctx.options.media_location);
	}

	aafi->ctx.options.media_location = (path) ? laaf_util_c99strdup (path) : NULL;

	return 0;
}

int
aafi_set_trace_class (AAF_Iface* aafi, const char* className)
{
	if (aafi->ctx.options.trace_class) {
		free (aafi->ctx.options.trace_class);
		aafi->ctx.options.trace_class = NULL;
	}

	aafi->ctx.options.trace_class = malloc ((strlen (className) + 1) * sizeof (wchar_t));

	if (aafi->ctx.options.trace_class == NULL) {
		return -1;
	}

	swprintf (aafi->ctx.options.trace_class, strlen (className) + 1, L"%" WPRIs, className);

	return 0;
}

void
aafi_release (AAF_Iface** aafi)
{
	if (*aafi == NULL)
		return;

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

	if ((*aafi)->compositionName != NULL) {
		free ((*aafi)->compositionName);
	}

	if ((*aafi)->Comments) {
		aafi_freeUserComments (&((*aafi)->Comments));
	}

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

		if ((*aafi)->Audio->Essences != NULL) {
			aafi_freeAudioEssences (&(*aafi)->Audio->Essences);
		}

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

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

		if ((*aafi)->Video->Essences != NULL) {
			aafi_freeVideoEssences (&(*aafi)->Video->Essences);
		}

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

	if ((*aafi)->Markers) {
		aafi_freeMarkers (&(*aafi)->Markers);
	}

	if ((*aafi)->ctx.options.trace_class) {
		free ((*aafi)->ctx.options.trace_class);
	}

	if ((*aafi)->ctx.options.media_location) {
		free ((*aafi)->ctx.options.media_location);
	}

	if ((*aafi)->Timecode != NULL) {
		free ((*aafi)->Timecode);
	}

	if ((*aafi)->dbg) {
		laaf_free_debug ((*aafi)->dbg);
	}

	free (*aafi);

	*aafi = NULL;
}

int
aafi_load_file (AAF_Iface* aafi, const char* file)
{
	if (aaf_load_file (aafi->aafd, file)) {
		error ("Could not load file : %s\n", file);
		return 1;
	}

	aafi_retrieveData (aafi);

	return 0;
}

aafiTransition*
aafi_get_fadein (aafiTimelineItem* audioItem)
{
	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_get_fadeout (aafiTimelineItem* audioItem)
{
	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;
}

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

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

	return NULL;
}

aafiMarker*
aafi_newMarker (AAF_Iface* aafi, aafRational_t* editRate, aafPosition_t start, aafPosition_t length, wchar_t* name, wchar_t* comment, uint16_t*(RVBColor[3]))
{
	aafiMarker* marker = malloc (sizeof (aafiMarker));

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

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

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

	if (RVBColor) {
		marker->RVBColor[0] = (*RVBColor)[0];
		marker->RVBColor[1] = (*RVBColor)[1];
		marker->RVBColor[2] = (*RVBColor)[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;
}

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

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

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

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

		free (marker);
	}

	*Markers = NULL;
}

aafiTimelineItem*
aafi_newTimelineItem (AAF_Iface* aafi, void* track, int itemType)
{
	aafiTimelineItem* item = NULL;

	if (itemType == AAFI_AUDIO_CLIP) {
		item = calloc (sizeof (aafiTimelineItem), sizeof (char));

		if (item == NULL) {
			error ("%s.", strerror (errno));
			return NULL;
		}

		item->type = AAFI_AUDIO_CLIP;

		item->data = calloc (sizeof (aafiAudioClip), sizeof (char));

		aafiAudioClip* audioClip = item->data;

		audioClip->track = (aafiAudioTrack*)track;
		audioClip->Item  = item;
	} else if (itemType == AAFI_VIDEO_CLIP) {
		item = calloc (sizeof (aafiTimelineItem), sizeof (char));

		if (item == NULL) {
			error ("%s.", strerror (errno));
			return NULL;
		}

		item->type = AAFI_VIDEO_CLIP;

		item->data = calloc (sizeof (aafiVideoClip), sizeof (char));

		aafiVideoClip* videoClip = item->data;

		videoClip->track = (aafiVideoTrack*)track;
	} else if (itemType == AAFI_TRANS) {
		item = calloc (sizeof (aafiTimelineItem), sizeof (char));

		if (item == NULL) {
			error ("%s.", strerror (errno));
			return NULL;
		}

		item->type = AAFI_TRANS;

		item->data = calloc (sizeof (aafiTransition), sizeof (char));
	}

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

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

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

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

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

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

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

	return item;
}

int
aafi_removeTimelineItem (AAF_Iface* aafi, aafiTimelineItem* item)
{
	if (item->prev != NULL) {
		item->prev->next = item->next;
	}

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

	aafiAudioTrack* audioTrack = NULL;

	foreach_audioTrack (audioTrack, aafi)
	{
		if (audioTrack->Items == item) {
			audioTrack->Items = item->next;
		}
	}

	aafi_freeTimelineItem (&item);

	return 0;
}

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

	if (gain->time != NULL) {
		free (gain->time);
	}

	if (gain->value != NULL) {
		free (gain->value);
	}

	free (gain);
}

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

void
aafi_freeAudioClip (aafiAudioClip* audioClip)
{
	if (audioClip->gain != NULL) {
		aafi_freeAudioGain (audioClip->gain);
	}

	if (audioClip->automation != NULL) {
		aafi_freeAudioGain (audioClip->automation);
	}
}

void
aafi_freeTimelineItem (aafiTimelineItem** item)
{
	if ((*item)->type == AAFI_TRANS) {
		aafi_freeTransition ((aafiTransition*)((*item)->data));
		free ((*item)->data);
	} else if ((*item)->type == AAFI_AUDIO_CLIP) {
		aafi_freeAudioClip ((aafiAudioClip*)((*item)->data));
		free ((*item)->data);
	} else if ((*item)->type == AAFI_VIDEO_CLIP) {
		free ((*item)->data);
	}

	free (*item);

	*item = NULL;
}

void
aafi_freeTimelineItems (aafiTimelineItem** items)
{
	aafiTimelineItem* item     = NULL;
	aafiTimelineItem* nextItem = NULL;

	for (item = (*items); item != NULL; item = nextItem) {
		nextItem = item->next;
		aafi_freeTimelineItem (&item);
	}

	*items = NULL;
}

aafiUserComment*
aafi_newUserComment (AAF_Iface* aafi, aafiUserComment** CommentList)
{
	aafiUserComment* UserComment = calloc (sizeof (aafiUserComment), 1);

	if (UserComment == NULL) {
		error ("%s.", strerror (errno));
		return NULL;
	}

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

	return UserComment;
}

void
aafi_freeUserComments (aafiUserComment** CommentList)
{
	aafiUserComment* UserComment = *CommentList;
	aafiUserComment* tmp         = NULL;

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

		if (tmp->name != NULL) {
			free (tmp->name);
		}

		if (tmp->text != NULL) {
			free (tmp->text);
		}

		free (tmp);
	}

	*CommentList = NULL;
}

void
aafi_freeTransition (aafiTransition* Transition)
{
	if (Transition->value_a != NULL) {
		free (Transition->value_a);
	}

	if (Transition->value_b != NULL) {
		free (Transition->value_b);
	}

	if (Transition->time_a != NULL) {
		free (Transition->time_a);
	}

	if (Transition->time_b != NULL) {
		free (Transition->time_b);
	}
}

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

	if (track == NULL) {
		error ("%s.", strerror (errno));
		return NULL;
	}

	track->Audio       = aafi->Audio;
	track->format      = AAFI_TRACK_FORMAT_NOT_SET;
	track->pan         = NULL;
	track->gain        = NULL;
	track->current_pos = 0;
	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;
}

void
aafi_freeAudioTracks (aafiAudioTrack** tracks)
{
	if (*(tracks) == NULL) {
		return;
	}

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

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

		if (track->name != NULL) {
			free (track->name);
		}

		if (track->gain != NULL) {
			aafi_freeAudioGain (track->gain);
		}

		if (track->pan != NULL) {
			aafi_freeAudioPan (track->pan);
		}

		if (track->Items != NULL) {
			aafi_freeTimelineItems (&(track->Items));
		}

		free (track);
	}

	*tracks = NULL;
}

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

	if (track == NULL) {
		error ("%s.", strerror (errno));
		return NULL;
	}

	track->Video       = aafi->Video;
	track->current_pos = 0;
	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;
}

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;

		if (track->name != NULL) {
			free (track->name);
		}

		if (track->Items != NULL) {
			aafi_freeTimelineItems (&(track->Items));
		}

		free (track);
	}

	*tracks = NULL;
}

aafiAudioEssence*
aafi_newAudioEssence (AAF_Iface* aafi)
{
	aafiAudioEssence* audioEssence = calloc (sizeof (aafiAudioEssence), sizeof (char));

	if (audioEssence == NULL) {
		error ("%s.", strerror (errno));
		return NULL;
	}

	audioEssence->next = aafi->Audio->Essences;

	audioEssence->original_file_path = NULL;
	audioEssence->usable_file_path   = NULL;
	audioEssence->file_name          = NULL;
	audioEssence->unique_file_name   = NULL;
	audioEssence->clip_count         = 0;
	audioEssence->user               = NULL;

	aafi->Audio->Essences = audioEssence;

	return audioEssence;
}

void
aafi_freeAudioEssences (aafiAudioEssence** audioEssence)
{
	if (*(audioEssence) == NULL) {
		return;
	}

	aafiAudioEssence* nextAudioEssence = NULL;

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

		if ((*audioEssence)->original_file_path != NULL) {
			free ((*audioEssence)->original_file_path);
		}

		if ((*audioEssence)->usable_file_path != NULL) {
			free ((*audioEssence)->usable_file_path);
		}

		if ((*audioEssence)->file_name != NULL) {
			free ((*audioEssence)->file_name);
		}

		if ((*audioEssence)->unique_file_name != NULL) {
			free ((*audioEssence)->unique_file_name);
		}

		free (*audioEssence);
	}

	*audioEssence = NULL;
}

aafiVideoEssence*
aafi_newVideoEssence (AAF_Iface* aafi)
{
	aafiVideoEssence* videoEssence = calloc (sizeof (aafiVideoEssence), sizeof (char));

	if (videoEssence == NULL) {
		error ("%s.", strerror (errno));
		return NULL;
	}

	videoEssence->next = aafi->Video->Essences;

	videoEssence->original_file_path = NULL;
	videoEssence->usable_file_path   = NULL;
	videoEssence->file_name          = NULL;
	videoEssence->unique_file_name   = NULL;

	aafi->Video->Essences = videoEssence;

	return videoEssence;
}

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

	aafiVideoEssence* nextVideoEssence = NULL;

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

		if ((*videoEssence)->original_file_path != NULL) {
			free ((*videoEssence)->original_file_path);
		}

		if ((*videoEssence)->usable_file_path != NULL) {
			free ((*videoEssence)->usable_file_path);
		}

		if ((*videoEssence)->file_name != NULL) {
			free ((*videoEssence)->file_name);
		}

		if ((*videoEssence)->unique_file_name != NULL) {
			free ((*videoEssence)->unique_file_name);
		}

		free (*videoEssence);
	}

	*videoEssence = NULL;
}

/**
 * @}
 */
