/**
 * Copyright (c) 2020 Filip Klembara (in2core)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#if RTC_ENABLE_MEDIA

#include "mediahandlerelement.hpp"

#include "impl/internals.hpp"

#include <cassert>

namespace rtc {

ChainedMessagesProduct make_chained_messages_product() {
	return std::make_shared<std::vector<binary_ptr>>();
}

ChainedMessagesProduct make_chained_messages_product(message_ptr msg) {
	std::vector<binary_ptr> msgs = {msg};
	return std::make_shared<std::vector<binary_ptr>>(msgs);
}

ChainedOutgoingProduct::ChainedOutgoingProduct(ChainedMessagesProduct messages, message_ptr control)
: messages(messages), control(control) { }

ChainedIncomingProduct::ChainedIncomingProduct(ChainedMessagesProduct incoming, ChainedMessagesProduct outgoing)
: incoming(incoming), outgoing(outgoing) { }

ChainedIncomingControlProduct::ChainedIncomingControlProduct(message_ptr incoming, optional<ChainedOutgoingProduct> outgoing)
: incoming(incoming), outgoing(outgoing) { }

MediaHandlerElement::MediaHandlerElement() { }

void MediaHandlerElement::removeFromChain() {
	if (upstream) {
		upstream->downstream = downstream;
	}
	if (downstream) {
		downstream->upstream = upstream;
	}
	upstream = nullptr;
	downstream = nullptr;
}

void MediaHandlerElement::recursiveRemoveChain() {
	if (downstream) {
		// `recursiveRemoveChain` removes last strong reference to downstream element
		// we need to keep strong reference to prevent deallocation of downstream element
		// during `recursiveRemoveChain`
		auto strongDownstreamPtr = downstream;
		downstream->recursiveRemoveChain();
	}
	removeFromChain();
}

optional<ChainedOutgoingProduct> MediaHandlerElement::processOutgoingResponse(ChainedOutgoingProduct messages) {
	if (messages.messages) {
		if (upstream) {
			auto msgs = upstream->formOutgoingBinaryMessage(ChainedOutgoingProduct(messages.messages, messages.control));
			if (msgs.has_value()) {
				return msgs.value();
			} else {
				LOG_ERROR << "Generating outgoing message failed";
				return nullopt;
			}
		} else {
			return messages;
		}
	} else if (messages.control) {
		if (upstream) {
			auto control = upstream->formOutgoingControlMessage(messages.control);
			if (control) {
				return ChainedOutgoingProduct(nullptr, control);
			} else {
				LOG_ERROR << "Generating outgoing control message failed";
				return nullopt;
			}
		} else {
			return messages;
		}
	} else {
		return ChainedOutgoingProduct();
	}
}

void MediaHandlerElement::prepareAndSendResponse(optional<ChainedOutgoingProduct> outgoing, std::function<bool (ChainedOutgoingProduct)> send) {
	if (outgoing.has_value()) {
		auto message = outgoing.value();
		auto response = processOutgoingResponse(message);
		if (response.has_value()) {
			if(!send(response.value())) {
				LOG_DEBUG << "Send failed";
			}
		} else {
			LOG_DEBUG << "No response to send";
		}
	}
}

message_ptr MediaHandlerElement::formIncomingControlMessage(message_ptr message, std::function<bool (ChainedOutgoingProduct)> send) {
	assert(message);
	auto product = processIncomingControlMessage(message);
	prepareAndSendResponse(product.outgoing, send);
	if (product.incoming) {
		if (downstream) {
			return downstream->formIncomingControlMessage(product.incoming, send);
		} else {
			return product.incoming;
		}
	} else {
		return nullptr;
	}
}

ChainedMessagesProduct MediaHandlerElement::formIncomingBinaryMessage(ChainedMessagesProduct messages, std::function<bool (ChainedOutgoingProduct)> send) {
	assert(messages && !messages->empty());
	auto product = processIncomingBinaryMessage(messages);
	prepareAndSendResponse(product.outgoing, send);
	if (product.incoming) {
		if (downstream) {
			return downstream->formIncomingBinaryMessage(product.incoming, send);
		} else {
			return product.incoming;
		}
	} else {
		return nullptr;
	}
}

message_ptr MediaHandlerElement::formOutgoingControlMessage(message_ptr message) {
	assert(message);
	auto newMessage = processOutgoingControlMessage(message);
	assert(newMessage);
	if(!newMessage) {
		LOG_ERROR << "Failed to generate outgoing message";
		return nullptr;
	}
	if (upstream) {
		return upstream->formOutgoingControlMessage(newMessage);
	} else {
		return newMessage;
	}
}

optional<ChainedOutgoingProduct> MediaHandlerElement::formOutgoingBinaryMessage(ChainedOutgoingProduct product) {
	assert(product.messages && !product.messages->empty());
	auto newProduct = processOutgoingBinaryMessage(product.messages, product.control);
	assert(!product.control || newProduct.control);
	assert(newProduct.messages && !newProduct.messages->empty());
	if (product.control && !newProduct.control) {
		LOG_ERROR << "Outgoing message must not remove control message";
		return nullopt;
	}
	if (!newProduct.messages || newProduct.messages->empty()) {
		LOG_ERROR << "Failed to generate message";
		return nullopt;
	}
	if (upstream) {
		return upstream->formOutgoingBinaryMessage(newProduct);
	} else {
		return newProduct;
	}
}

ChainedIncomingControlProduct MediaHandlerElement::processIncomingControlMessage(message_ptr messages) {
	return {messages};
}

message_ptr MediaHandlerElement::processOutgoingControlMessage(message_ptr messages) {
	return messages;
}

ChainedIncomingProduct MediaHandlerElement::processIncomingBinaryMessage(ChainedMessagesProduct messages) {
	return {messages};
}

ChainedOutgoingProduct MediaHandlerElement::processOutgoingBinaryMessage(ChainedMessagesProduct messages, message_ptr control) {
	return {messages, control};
}

shared_ptr<MediaHandlerElement> MediaHandlerElement::chainWith(shared_ptr<MediaHandlerElement> upstream) {
	assert(this->upstream == nullptr);
	assert(upstream->downstream == nullptr);
	this->upstream = upstream;
	upstream->downstream = shared_from_this();
	return upstream;
}

} // namespace rtc

#endif /* RTC_ENABLE_MEDIA */
