/*
 * Copyright 2015, Dario Casalinuovo. All rights reserved.
 * Distributed under the terms of the MIT License.
 */
 
#include "MediaClient.h"
 
#include <MediaConnection.h>
 
#include <MediaRoster.h>
#include <TimeSource.h>
 
#include "MediaClientNode.h"
 
#include "MediaDebug.h"
 
 
namespace BPrivate { namespace media {
 
 
class ConnReleaser {
public:
	ConnReleaser(BMediaConnection* conn)
		:
		fConn(conn) {}
 
	virtual ~ConnReleaser()
	{
		fConn->Release();
	}
 
	bool operator== (const ConnReleaser &c1)
	{
		return c1.fConn == this->fConn;
	}
 
protected:
	BMediaConnection* Obj() const
	{
		return fConn;
	}
 
private:
	BMediaConnection* fConn;
};
 
 
class InputReleaser : public ConnReleaser {
public:
	InputReleaser(BMediaInput* input)
		:
		ConnReleaser(input) {}
 
	BMediaInput* Obj() const
	{
		return dynamic_cast<BMediaInput*>(ConnReleaser::Obj());
	}
};
 
 
class OutputReleaser : public ConnReleaser {
public:
	OutputReleaser(BMediaOutput* output)
		:
		ConnReleaser(output) {}
 
	BMediaOutput* Obj() const
	{
		return dynamic_cast<BMediaOutput*>(ConnReleaser::Obj());
	}
};
 
 
}
}
 
 
BMediaClient::BMediaClient(const char* name,
	media_type type, media_client_kinds kinds)
	:
	fLastID(-1)
{
	CALLED();
 
	fNode = new BMediaClientNode(name, this, type);
	_Init();
 
	fClient.node = fNode->Node();
	fClient.kinds = kinds;
}
 
 
BMediaClient::~BMediaClient()
{
	CALLED();
 
	_Deinit();
}
 
 
const media_client&
BMediaClient::Client() const
{
	return fClient;
}
 
 
status_t
BMediaClient::InitCheck() const
{
	CALLED();
 
	return fInitErr;
}
 
 
media_client_kinds
BMediaClient::Kinds() const
{
	CALLED();
 
	return fClient.Kinds();
}
 
 
media_type
BMediaClient::MediaType() const
{
	CALLED();
 
	// Right now ConsumerType() and ProducerType() are the same.
	return fNode->ConsumerType();
}
 
 
status_t
BMediaClient::RegisterInput(BMediaInput* input)
{
	input->_ConnectionRegistered(this, ++fLastID);
	_AddInput(input);
	return B_OK;
}
 
 
status_t
BMediaClient::RegisterOutput(BMediaOutput* output)
{
	output->_ConnectionRegistered(this, ++fLastID);
	_AddOutput(output);
	return B_OK;
}
 
 
status_t
BMediaClient::Bind(BMediaInput* input, BMediaOutput* output)
{
	CALLED();
 
	if (input == NULL
		|| output == NULL)
		return B_ERROR;
 
	if (input->fOwner != this || output->fOwner != this)
		return B_ERROR;
 
	// TODO: Implement binding one input to more outputs.
	if (input->fBind != NULL
		|| output->fBind != NULL)
		return B_ERROR;
 
	input->fBind = output;
	output->fBind = input;
	return B_OK;
}
 
 
status_t
BMediaClient::Unbind(BMediaInput* input, BMediaOutput* output)
{
	CALLED();
 
	if (input == NULL
		|| input == NULL)
		return B_ERROR;
 
	if (input->fOwner != this || output->fOwner != this)
		return B_ERROR;
 
	input->fBind = NULL;
	output->fBind = NULL;
	return B_OK;
}
 
 
status_t
BMediaClient::Connect(BMediaConnection* ourConnection,
	BMediaConnection* theirConnection)
{
	CALLED();
 
	return Connect(ourConnection, theirConnection->Connection());
}
 
 
status_t
BMediaClient::Connect(BMediaConnection* ourConnection,
	const media_connection& theirConnection)
{
	CALLED();
 
	BMediaOutput* output = dynamic_cast<BMediaOutput*>(ourConnection);
	if (output != NULL && theirConnection.IsInput())
		return _ConnectInput(output, theirConnection);
 
	BMediaInput* input = dynamic_cast<BMediaInput*>(ourConnection);
	if (input != NULL && theirConnection.IsOutput())
		return _ConnectOutput(input, theirConnection);
 
	return B_ERROR;
}
 
 
status_t
BMediaClient::Connect(BMediaConnection* connection,
	const media_client& client)
{
	UNIMPLEMENTED();
 
	return B_ERROR;
}
 
 
status_t
BMediaClient::Disconnect()
{
	CALLED();
 
	for (int32 i = 0; i < CountInputs(); i++)
		InputAt(i)->Disconnect();
 
	for (int32 i = 0; i < CountOutputs(); i++)
		OutputAt(i)->Disconnect();
 
	return B_OK;
}
 
 
int32
BMediaClient::CountInputs() const
{
	CALLED();
 
	return fInputs.CountItems();
}
 
 
int32
BMediaClient::CountOutputs() const
{
	CALLED();
 
	return fOutputs.CountItems();
}
 
 
BMediaInput*
BMediaClient::InputAt(int32 index) const
{
	CALLED();
 
	return fInputs.ItemAt(index)->Obj();
}
 
 
BMediaOutput*
BMediaClient::OutputAt(int32 index) const
{
	CALLED();
 
	return fOutputs.ItemAt(index)->Obj();
}
 
 
BMediaInput*
BMediaClient::FindInput(const media_connection& input) const
{
	CALLED();
 
	if (!input.IsInput())
		return NULL;
 
	return _FindInput(input.destination);
}
 
 
BMediaOutput*
BMediaClient::FindOutput(const media_connection& output) const
{
	CALLED();
 
	if (!output.IsOutput())
		return NULL;
 
	return _FindOutput(output.source);
}
 
 
bool
BMediaClient::IsStarted() const
{
	CALLED();
 
	return fRunning;
}
 
 
void
BMediaClient::ClientRegistered()
{
	CALLED();
}
 
 
status_t
BMediaClient::Start()
{
	CALLED();
 
	status_t err = B_OK;
	for (int32 i = 0; i < CountOutputs(); i++) {
		media_node remoteNode = OutputAt(i)->Connection().remote_node;
		if (remoteNode.kind & B_TIME_SOURCE)
			err = BMediaRoster::CurrentRoster()->StartTimeSource(
				remoteNode, BTimeSource::RealTime());
		else
			err = BMediaRoster::CurrentRoster()->StartNode(
				remoteNode, fNode->TimeSource()->Now());
	}
 
	return BMediaRoster::CurrentRoster()->StartNode(
		fNode->Node(), fNode->TimeSource()->Now());
}
 
 
status_t
BMediaClient::Stop()
{
	CALLED();
 
	return BMediaRoster::CurrentRoster()->StopNode(
		fNode->Node(), fNode->TimeSource()->Now());
}
 
 
status_t
BMediaClient::Seek(bigtime_t mediaTime,
	bigtime_t performanceTime)
{
	CALLED();
 
	return BMediaRoster::CurrentRoster()->SeekNode(fNode->Node(),
		mediaTime, performanceTime);
}
 
 
status_t
BMediaClient::Roll(bigtime_t start, bigtime_t stop, bigtime_t seek)
{
	CALLED();
 
	return BMediaRoster::CurrentRoster()->RollNode(fNode->Node(),
		start, stop, seek);
}
 
 
bigtime_t
BMediaClient::CurrentTime() const
{
	CALLED();
 
	return fCurrentTime;
}
 
 
BMediaAddOn*
BMediaClient::AddOn(int32* id) const
{
	CALLED();
 
	return NULL;
}
 
 
void
BMediaClient::HandleStart(bigtime_t performanceTime)
{
	fRunning = true;
}
 
 
void
BMediaClient::HandleStop(bigtime_t performanceTime)
{
	fRunning = false;
}
 
 
void
BMediaClient::HandleSeek(bigtime_t mediaTime, bigtime_t performanceTime)
{
}
 
 
status_t
BMediaClient::FormatSuggestion(media_type type, int32 quality,
	media_format* format)
{
	return B_ERROR;
}
 
 
void
BMediaClient::_Init()
{
	CALLED();
 
	BMediaRoster* roster = BMediaRoster::Roster(&fInitErr);
	if (fInitErr == B_OK && roster != NULL)
		fInitErr = roster->RegisterNode(fNode);
}
 
 
void
BMediaClient::_Deinit()
{
	CALLED();
 
	if (IsStarted())
		Stop();
 
	Disconnect();
 
	// This will release the connections too.
	fInputs.MakeEmpty(true);
	fOutputs.MakeEmpty(true);
 
	fNode->Release();
}
 
 
void
BMediaClient::_AddInput(BMediaInput* input)
{
	CALLED();
 
	fInputs.AddItem(new InputReleaser(input));
}
 
 
void
BMediaClient::_AddOutput(BMediaOutput* output)
{
	CALLED();
 
	fOutputs.AddItem(new OutputReleaser(output));
}
 
 
BMediaInput*
BMediaClient::_FindInput(const media_destination& dest) const
{
	CALLED();
 
	for (int32 i = 0; i < CountInputs(); i++) {
		if (dest.id == InputAt(i)->_Destination().id)
			return InputAt(i);
	}
	return NULL;
}
 
 
BMediaOutput*
BMediaClient::_FindOutput(const media_source& source) const
{
	CALLED();
 
	for (int32 i = 0; i < CountOutputs(); i++) {
		if (source.id == OutputAt(i)->_Source().id)
			return OutputAt(i);
	}
	return NULL;
}
 
 
status_t
BMediaClient::_ConnectInput(BMediaOutput* output,
	const media_connection& input)
{
	CALLED();
 
	if (input.destination == media_destination::null)
		return B_MEDIA_BAD_DESTINATION;
 
	media_output ourOutput = output->Connection()._BuildMediaOutput();
	media_input theirInput = input._BuildMediaInput();
	media_format format;
 
	// NOTE: We want to set this data in the callbacks if possible.
	// The correct format should have been set in BMediaConnection::Connected.
	// TODO: Perhaps add some check assert?
 
	status_t ret = BMediaRoster::CurrentRoster()->Connect(ourOutput.source,
		theirInput.destination, &format, &ourOutput, &theirInput,
		BMediaRoster::B_CONNECT_MUTED);
 
#if 0
	if (ret == B_OK)
		output->fConnection.format = format;
#endif
 
	return ret;
}
 
 
status_t
BMediaClient::_ConnectOutput(BMediaInput* input,
	const media_connection& output)
{
	CALLED();
 
	if (output.source == media_source::null)
		return B_MEDIA_BAD_SOURCE;
 
	media_input ourInput = input->Connection()._BuildMediaInput();
	media_output theirOutput = output._BuildMediaOutput();
	media_format format;
 
	// NOTE: We want to set this data in the callbacks if possible.
	// The correct format should have been set in BMediaConnection::Connected.
	// TODO: Perhaps add some check assert?
 
	status_t ret = BMediaRoster::CurrentRoster()->Connect(theirOutput.source,
		ourInput.destination, &format, &theirOutput, &ourInput,
		BMediaRoster::B_CONNECT_MUTED);
 
#if 0
	if (ret == B_OK)
		input->fConnection.format = format;
#endif
 
	return ret;
}
 
 
status_t
BMediaClient::_DisconnectConnection(BMediaConnection* conn)
{
	CALLED();
 
	if (conn->Client() != this)
		return B_ERROR;
 
	const media_connection& handle = conn->Connection();
	if (handle.IsInput()) {
		return BMediaRoster::CurrentRoster()->Disconnect(
			handle.remote_node.node, handle.source,
			handle._Node().node, handle.destination);
	} else {
		return BMediaRoster::CurrentRoster()->Disconnect(
			handle._Node().node, handle.source,
			handle.remote_node.node, handle.destination);
	}
 
	return B_ERROR;
}
 
 
status_t
BMediaClient::_ReleaseConnection(BMediaConnection* conn)
{
	if (conn->Client() != this)
		return B_ERROR;
 
	if (conn->Connection().IsInput()) {
		InputReleaser obj = InputReleaser(dynamic_cast<BMediaInput*>(conn));
		fInputs.RemoveItem(&obj);
		return B_OK;
	} else {
		OutputReleaser obj = OutputReleaser(dynamic_cast<BMediaOutput*>(conn));
		fOutputs.RemoveItem(&obj);
		return B_OK;
	}
 
	return B_ERROR;
}
 
 
void BMediaClient::_ReservedMediaClient0() {}
void BMediaClient::_ReservedMediaClient1() {}
void BMediaClient::_ReservedMediaClient2() {}
void BMediaClient::_ReservedMediaClient3() {}
void BMediaClient::_ReservedMediaClient4() {}
void BMediaClient::_ReservedMediaClient5() {}
void BMediaClient::_ReservedMediaClient6() {}
void BMediaClient::_ReservedMediaClient7() {}
void BMediaClient::_ReservedMediaClient8() {}
void BMediaClient::_ReservedMediaClient9() {}
void BMediaClient::_ReservedMediaClient10() {}

V501 There are identical sub-expressions to the left and to the right of the '||' operator: input == __null || input == __null