/*
 * Copyright 2004-2009, The Haiku Project. All rights reserved.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		Axel Dörfler
 *		Marcus Overhagen
 */
 
 
#include "AddOnManager.h"
#include "DataExchange.h"
#include "FormatManager.h"
#include "MetaFormat.h"
#include "MediaDebug.h"
 
#include <MediaFormats.h>
#include <ObjectList.h>
#include <Message.h>
#include <Autolock.h>
 
#include <string.h>
 
using namespace BPrivate::media;
 
 
static BLocker sLock;
static BObjectList<meta_format> sFormats;
static bigtime_t sLastFormatsUpdate;
 
 
status_t
get_next_encoder(int32* cookie, const media_file_format* fileFormat,
	const media_format* inputFormat, media_format* _outputFormat,
	media_codec_info* _codecInfo)
{
	// TODO: If fileFormat is provided (existing apps also pass NULL),
	// we could at least check fileFormat->capabilities against
	// outputFormat->type without even contacting the server.
 
	if (cookie == NULL || inputFormat == NULL || _codecInfo == NULL)
		return B_BAD_VALUE;
 
	while (true) {
		media_codec_info candidateCodecInfo;
		media_format_family candidateFormatFamily;
		media_format candidateInputFormat;
		media_format candidateOutputFormat;
 
		status_t ret = AddOnManager::GetInstance()->GetCodecInfo(
			&candidateCodecInfo, &candidateFormatFamily,
			&candidateInputFormat, &candidateOutputFormat, *cookie);
 
		if (ret != B_OK)
			return ret;
 
		*cookie = *cookie + 1;
 
		if (fileFormat != NULL && candidateFormatFamily != B_ANY_FORMAT_FAMILY
			&& fileFormat->family != B_ANY_FORMAT_FAMILY
			&& fileFormat->family != candidateFormatFamily) {
			continue;
		}
 
		if (!candidateInputFormat.Matches(inputFormat))
			continue;
 
		if (_outputFormat != NULL)
			*_outputFormat = candidateOutputFormat;
 
		*_codecInfo = candidateCodecInfo;
		break;
	}
 
	return B_OK;
}
 
 
status_t
get_next_encoder(int32* cookie, const media_file_format* fileFormat,
	const media_format* inputFormat, const media_format* outputFormat,
	media_codec_info* _codecInfo, media_format* _acceptedInputFormat,
	media_format* _acceptedOutputFormat)
{
	// TODO: If fileFormat is provided (existing apps also pass NULL),
	// we could at least check fileFormat->capabilities against
	// outputFormat->type without even contacting the server.
 
	if (cookie == NULL || inputFormat == NULL || outputFormat == NULL
		|| _codecInfo == NULL) {
		return B_BAD_VALUE;
	}
 
	while (true) {
		media_codec_info candidateCodecInfo;
		media_format_family candidateFormatFamily;
		media_format candidateInputFormat;
		media_format candidateOutputFormat;
 
		status_t ret = AddOnManager::GetInstance()->GetCodecInfo(
			&candidateCodecInfo, &candidateFormatFamily, &candidateInputFormat,
			&candidateOutputFormat, *cookie);
				
		if (ret != B_OK)
			return ret;
 
		*cookie = *cookie + 1;
 
		if (fileFormat != NULL && candidateFormatFamily != B_ANY_FORMAT_FAMILY
			&& fileFormat->family != B_ANY_FORMAT_FAMILY
			&& fileFormat->family != candidateFormatFamily) {
			continue;
		}
 
		if (!candidateInputFormat.Matches(inputFormat)
			|| !candidateOutputFormat.Matches(outputFormat)) {
			continue;
		}
 
		// TODO: These formats are currently way too generic. For example,
		// an encoder may want to adjust video width to a multiple of 16,
		// or overwrite the intput and or output color space. To make this
		// possible, we actually have to instantiate an Encoder here and
		// ask it to specifiy the format.
		if (_acceptedInputFormat != NULL)
			*_acceptedInputFormat = candidateInputFormat;
		if (_acceptedOutputFormat != NULL)
			*_acceptedOutputFormat = candidateOutputFormat;
 
		*_codecInfo = candidateCodecInfo;
		break;
	}
 
	return B_OK;
}
 
 
status_t
get_next_encoder(int32* cookie, media_codec_info* _codecInfo)
{
	if (cookie == NULL || _codecInfo == NULL)
		return B_BAD_VALUE;
 
	media_format_family formatFamily;
	media_format inputFormat;
	media_format outputFormat;
 
	status_t ret = AddOnManager::GetInstance()->GetCodecInfo(_codecInfo,
		&formatFamily, &inputFormat, &outputFormat, *cookie);
	if (ret != B_OK)
		return ret;
 
	*cookie = *cookie + 1;
 
	return B_OK;
}
 
 
bool
does_file_accept_format(const media_file_format* _fileFormat,
	media_format* format, uint32 flags)
{
	UNIMPLEMENTED();
	return false;
}
 
 
//	#pragma mark -
 
 
_media_format_description::_media_format_description()
{
	memset(this, 0, sizeof(*this));
}
 
 
_media_format_description::~_media_format_description()
{
}
 
 
_media_format_description::_media_format_description(
	const _media_format_description& other)
{
	memcpy(this, &other, sizeof(*this));
}
 
 
_media_format_description& 
_media_format_description::operator=(const _media_format_description& other)
{
	memcpy(this, &other, sizeof(*this));
	return *this;
}
 
 
bool
operator==(const media_format_description& a,
	const media_format_description& b)
{
	if (a.family != b.family)
		return false;
 
	switch (a.family) {
		case B_BEOS_FORMAT_FAMILY:
			return a.u.beos.format == b.u.beos.format;
		case B_QUICKTIME_FORMAT_FAMILY:
			return a.u.quicktime.codec == b.u.quicktime.codec
				&& a.u.quicktime.vendor == b.u.quicktime.vendor;
		case B_AVI_FORMAT_FAMILY:
			return a.u.avi.codec == b.u.avi.codec;
		case B_ASF_FORMAT_FAMILY:
			return a.u.asf.guid == b.u.asf.guid;
		case B_MPEG_FORMAT_FAMILY:
			return a.u.mpeg.id == b.u.mpeg.id;
		case B_WAV_FORMAT_FAMILY:
			return a.u.wav.codec == b.u.wav.codec;
		case B_AIFF_FORMAT_FAMILY:
			return a.u.aiff.codec == b.u.aiff.codec;
		case B_AVR_FORMAT_FAMILY:
			return a.u.avr.id == b.u.avr.id;
		case B_MISC_FORMAT_FAMILY:
			return a.u.misc.file_format == b.u.misc.file_format
				&& a.u.misc.codec == b.u.misc.codec;
 
		default:
			return false;
	}
}
 
 
bool
operator<(const media_format_description& a, const media_format_description& b)
{
	if (a.family != b.family)
		return a.family < b.family;
 
	switch (a.family) {
		case B_BEOS_FORMAT_FAMILY:
			return a.u.beos.format < b.u.beos.format;
		case B_QUICKTIME_FORMAT_FAMILY:
			if (a.u.quicktime.vendor == b.u.quicktime.vendor)
				return a.u.quicktime.codec < b.u.quicktime.codec;
			return a.u.quicktime.vendor < b.u.quicktime.vendor;
		case B_AVI_FORMAT_FAMILY:
			return a.u.avi.codec < b.u.avi.codec;
		case B_ASF_FORMAT_FAMILY:
			return a.u.asf.guid < b.u.asf.guid;
		case B_MPEG_FORMAT_FAMILY:
			return a.u.mpeg.id < b.u.mpeg.id;
		case B_WAV_FORMAT_FAMILY:
			return a.u.wav.codec < b.u.wav.codec;
		case B_AIFF_FORMAT_FAMILY:
			return a.u.aiff.codec < b.u.aiff.codec;
		case B_AVR_FORMAT_FAMILY:
			return a.u.avr.id < b.u.avr.id;
		case B_MISC_FORMAT_FAMILY:
			if (a.u.misc.file_format == b.u.misc.file_format)
				return a.u.misc.codec < b.u.misc.codec;
			return a.u.misc.file_format < b.u.misc.file_format;
 
		default:
			return true;
	}
}
 
 
bool
operator==(const GUID& a, const GUID& b)
{
	return memcmp(&a, &b, sizeof(a)) == 0;
}
 
 
bool
operator<(const GUID& a, const GUID& b)
{
	return memcmp(&a, &b, sizeof(a)) < 0;
}
 
 
//	#pragma mark -
//
//	Some (meta) formats supply functions
 
 
meta_format::meta_format()
	:
	id(0)
{
}
 
 
 
meta_format::meta_format(const media_format_description& description,
	const media_format& format, int32 id)
	:
	description(description),
	format(format),
	id(id)
{
}
 
 
meta_format::meta_format(const media_format_description& description)
	:
	description(description),
	id(0)
{
}
 
 
meta_format::meta_format(const meta_format& other)
	:
	description(other.description),
	format(other.format)
{
}
 
 
bool 
meta_format::Matches(const media_format& otherFormat,
	media_format_family family)
{
	if (family != description.family)
		return false;
 
	return format.Matches(&otherFormat);
}
 
 
int 
meta_format::CompareDescriptions(const meta_format* a, const meta_format* b)
{
	if (a->description == b->description)
		return 0;
 
	if (a->description < b->description)
		return -1;
 
	return 1;
}
 
 
int 
meta_format::Compare(const meta_format* a, const meta_format* b)
{
	int compare = CompareDescriptions(a, b);
	if (compare != 0)
		return compare;
 
	return a->id - b->id;
}
 
 
/** We share one global list for all BMediaFormats in the team - since the
 *	format data can change at any time, we have to update the list to ensure
 *	that we are working on the latest data set. The list is always sorted by
 *	description. The formats lock has to be held when you call this function.
 */
static status_t
update_media_formats()
{
	if (!sLock.IsLocked())
		return B_NOT_ALLOWED;
 
	// We want the add-ons to register themselves with the format manager, so
	// the list is up to date.
	AddOnManager::GetInstance()->RegisterAddOns();
 
	BMessage reply;
	FormatManager::GetInstance()->GetFormats(sLastFormatsUpdate, reply);
 
	// do we need an update at all?
	bool needUpdate;
	if (reply.FindBool("need_update", &needUpdate) < B_OK)
		return B_ERROR;
	if (!needUpdate)
		return B_OK;
 
	// update timestamp and check if the message is okay
	type_code code;
	int32 count;
	if (reply.FindInt64("timestamp", &sLastFormatsUpdate) < B_OK
		|| reply.GetInfo("formats", &code, &count) < B_OK)
		return B_ERROR;
 
	// overwrite already existing formats
 
	int32 index = 0;
	for (; index < sFormats.CountItems() && index < count; index++) {
		meta_format* item = sFormats.ItemAt(index);
 
		const meta_format* newItem;
		ssize_t size;
		if (reply.FindData("formats", MEDIA_META_FORMAT_TYPE, index,
				(const void**)&newItem, &size) == B_OK)
			*item = *newItem;
	}
 
	// allocate additional formats
 
	for (; index < count; index++) {
		const meta_format* newItem;
		ssize_t size;
		if (reply.FindData("formats", MEDIA_META_FORMAT_TYPE, index,
				(const void**)&newItem, &size) == B_OK)
			sFormats.AddItem(new meta_format(*newItem));
	}
 
	// remove no longer used formats
 
	while (count < sFormats.CountItems())
		delete sFormats.RemoveItemAt(count);
 
	return B_OK;
}
 
 
//	#pragma mark -
 
 
BMediaFormats::BMediaFormats()
	:
	fIteratorIndex(0)	
{
}
 
 
BMediaFormats::~BMediaFormats()
{
}
 
 
status_t 
BMediaFormats::InitCheck()
{
	return sLock.Sem() >= B_OK ? B_OK : sLock.Sem();
}
 
 
status_t 
BMediaFormats::GetCodeFor(const media_format& format,
	media_format_family family,
	media_format_description* _description)
{
	BAutolock locker(sLock);
 
	status_t status = update_media_formats();
	if (status < B_OK)
		return status;
 
	// search for a matching format
 
	for (int32 index = sFormats.CountItems(); index-- > 0;) {
		meta_format* metaFormat = sFormats.ItemAt(index);
 
		if (metaFormat->Matches(format, family)) {
			*_description = metaFormat->description;
			return B_OK;
		}
	}
 
	return B_MEDIA_BAD_FORMAT;
}
 
 
status_t 
BMediaFormats::GetFormatFor(const media_format_description& description,
	media_format* _format)
{
	BAutolock locker(sLock);
 
	status_t status = update_media_formats();
	if (status < B_OK) {
		ERROR("BMediaFormats: updating formats from server failed: %s!\n",
			strerror(status));
		return status;
	}
	TRACE("search for description family = %d, a = 0x%"
		B_PRId32 "x, b = 0x%" B_PRId32 "x\n",
		description.family, description.u.misc.file_format,
		description.u.misc.codec);
 
	// search for a matching format description
 
	meta_format other(description);
	const meta_format* metaFormat = sFormats.BinarySearch(other,
		meta_format::CompareDescriptions);
	TRACE("meta format == %p\n", metaFormat);
	if (metaFormat == NULL) {
		_format->Clear(); // clear to widlcard
		return B_MEDIA_BAD_FORMAT;
	}
 
	// found it!
	*_format = metaFormat->format;
	return B_OK;
}
 
 
status_t 
BMediaFormats::GetBeOSFormatFor(uint32 format, 
	media_format* _format, media_type type)
{
	BMediaFormats formats;
 
	media_format_description description;
	description.family = B_BEOS_FORMAT_FAMILY;
	description.u.beos.format = format;
 
	status_t status = formats.GetFormatFor(description, _format);
	if (status < B_OK)
		return status;
 
	if (type != B_MEDIA_UNKNOWN_TYPE && type != _format->type)
		return B_BAD_TYPE;
 
	return B_OK;
}
 
 
status_t 
BMediaFormats::GetAVIFormatFor(uint32 codec,
	media_format* _format, media_type type)
{
	UNIMPLEMENTED();
	BMediaFormats formats;
 
	media_format_description description;
	description.family = B_AVI_FORMAT_FAMILY;
	description.u.avi.codec = codec;
 
	status_t status = formats.GetFormatFor(description, _format);
	if (status < B_OK)
		return status;
 
	if (type != B_MEDIA_UNKNOWN_TYPE && type != _format->type)
		return B_BAD_TYPE;
 
	return B_OK;
}
 
 
status_t 
BMediaFormats::GetQuicktimeFormatFor(uint32 vendor, uint32 codec, 
	media_format* _format, media_type type)
{
	BMediaFormats formats;
 
	media_format_description description;
	description.family = B_QUICKTIME_FORMAT_FAMILY;
	description.u.quicktime.vendor = vendor;
	description.u.quicktime.codec = codec;
 
	status_t status = formats.GetFormatFor(description, _format);
	if (status < B_OK)
		return status;
 
	if (type != B_MEDIA_UNKNOWN_TYPE && type != _format->type)
		return B_BAD_TYPE;
 
	return B_OK;
}
 
 
status_t 
BMediaFormats::RewindFormats()
{
	if (!sLock.IsLocked() || sLock.LockingThread() != find_thread(NULL)) {
		// TODO: Shouldn't we simply drop into the debugger in this case?
		return B_NOT_ALLOWED;
	}
 
	fIteratorIndex = 0;
	return B_OK;
}
 
 
status_t 
BMediaFormats::GetNextFormat(media_format* _format,
	media_format_description* _description)
{
	if (!sLock.IsLocked() || sLock.LockingThread() != find_thread(NULL)) {
		// TODO: Shouldn't we simply drop into the debugger in this case?
		return B_NOT_ALLOWED;
	}
 
	if (fIteratorIndex == 0) {
		// This is the first call, so let's make sure we have current data to
		// operate on.
		status_t status = update_media_formats();
		if (status < B_OK)
			return status;
	}
 
	meta_format* format = sFormats.ItemAt(fIteratorIndex++);
	if (format == NULL)
		return B_BAD_INDEX;
 
	return B_OK;
}
 
 
bool
BMediaFormats::Lock()
{
	return sLock.Lock();
}
 
 
void 
BMediaFormats::Unlock()
{
	sLock.Unlock();
}
 
 
status_t 
BMediaFormats::MakeFormatFor(const media_format_description* descriptions,
	int32 descriptionCount, media_format* format, uint32 flags,
	void* _reserved)
{
	status_t status = FormatManager::GetInstance()->MakeFormatFor(descriptions,
		descriptionCount, *format, flags, _reserved);
 
	return status;
}
 
 
// #pragma mark - deprecated API
 
 
status_t 
BMediaFormats::MakeFormatFor(const media_format_description& description,
	const media_format& inFormat, media_format* _outFormat)
{
	*_outFormat = inFormat;
	return MakeFormatFor(&description, 1, _outFormat);
}
 

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: id.