/*
 * Copyright (c) 2010-2019 Belledonne Communications SARL.
 *
 * This file is part of Liblinphone.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <cmath>

#include "private.h"

#include "logger/logger.h"

#include "payload-type-handler.h"

// =============================================================================

using namespace std;

LINPHONE_BEGIN_NAMESPACE

const int PayloadTypeHandler::udpHeaderSize = 8;
const int PayloadTypeHandler::rtpHeaderSize = 12;

// 20 is the minimum, but there may be some options.
const int PayloadTypeHandler::ipv4HeaderSize = 20;

const VbrCodecBitrate PayloadTypeHandler::defaultVbrCodecBitrates[] = {
	{ 64, 44100, 50 },
	{ 64, 16000, 40 },
	{ 32, 16000, 32 },
	{ 32, 8000, 32 },
	{ 0, 8000, 24 },
	{ 0, 0, 0 }
};

// =============================================================================

int PayloadTypeHandler::findPayloadTypeNumber (const std::list<OrtpPayloadType*> & assigned, const OrtpPayloadType *pt) {
	const OrtpPayloadType *candidate = nullptr;
	for (const auto & it : assigned) {
		if (
			strcasecmp(pt->mime_type, payload_type_get_mime(it)) == 0 &&
			it->clock_rate == pt->clock_rate &&
			((it->channels == pt->channels) || (pt->channels <= 0))
		) {
			candidate = it;
			if (
				(it->recv_fmtp && pt->recv_fmtp && (strcasecmp(it->recv_fmtp, pt->recv_fmtp) == 0)) ||
				(!it->recv_fmtp && !pt->recv_fmtp)
			)
				// Exact match.
				break;
		}
	}
	return candidate ? payload_type_get_number(candidate) : -1;
}

bool PayloadTypeHandler::hasTelephoneEventPayloadType (const std::list<OrtpPayloadType*> & tev, int rate) {
	for (const auto & pt : tev) {
		if (pt->clock_rate == rate)
			return true;
	}
	return false;
}

bool PayloadTypeHandler::isPayloadTypeUsableForBandwidth (const OrtpPayloadType *pt, int bandwidthLimit) {
	const int videoEnablementLimit = 99;
	double codecBand = 0;
	switch (pt->type) {
		case PAYLOAD_AUDIO_CONTINUOUS:
		case PAYLOAD_AUDIO_PACKETIZED:
			codecBand = getAudioPayloadTypeBandwidth(pt, bandwidthLimit);
			return bandwidthIsGreater(bandwidthLimit, (int)codecBand);
		case PAYLOAD_VIDEO:
			// Infinite or greater than videoEnablementLimit.
			if (bandwidthLimit <= 0 || bandwidthLimit >= videoEnablementLimit)
				return true;
			break;
		case PAYLOAD_TEXT:
			return true;
	}
	return false;
}

int PayloadTypeHandler::lookupTypicalVbrBitrate (int maxBandwidth, int clockRate) {
	if (maxBandwidth <= 0)
		maxBandwidth = defaultVbrCodecBitrates[0].maxAvailableBitrate;
	for (const VbrCodecBitrate *it = &defaultVbrCodecBitrates[0]; it->minClockRate != 0; it++) {
		if ((maxBandwidth >= it->maxAvailableBitrate) && (clockRate >= it->minClockRate))
			return it->recommendedBitrate;
	}
	lError() << "lookupTypicalVbrBitrate(): should not happen";
	return 32;
}

// -----------------------------------------------------------------------------

void PayloadTypeHandler::assignPayloadTypeNumbers (const std::list<OrtpPayloadType*> & codecs) {
	OrtpPayloadType *red = nullptr;
	OrtpPayloadType *t140 = nullptr;

	for (const auto & pt : codecs) {
		int number = payload_type_get_number(pt);

		// Check if number is duplicated: it could be the case if the remote forced us to use a mapping with a previous offer.
		if ((number != -1) && !(pt->flags & PAYLOAD_TYPE_FROZEN_NUMBER)) {
			if (!isPayloadTypeNumberAvailable(codecs, number, pt)) {
				lInfo() << "Reassigning payload type " << number << " " << pt->mime_type << "/" << pt->clock_rate << " because already offered";
				// Need to be re-assigned.
				number = -1;
			}
		}

		if (number == -1) {
			int dynNumber = getCore()->getCCore()->codecs_conf.dyn_pt;
			while (dynNumber < 127) {
				if (isPayloadTypeNumberAvailable(codecs, dynNumber, nullptr)) {
					payload_type_set_number(pt, dynNumber);
					dynNumber++;
					break;
				}
				dynNumber++;
			}
			if (dynNumber == 127) {
				lError() << "Too many payload types configured ! codec " << pt->mime_type << "/" << pt->clock_rate << " is disabled";
				payload_type_set_enable(pt, false);
			}
		}

		if (strcmp(pt->mime_type, payload_type_t140_red.mime_type) == 0)
			red = pt;
		else if (strcmp(pt->mime_type, payload_type_t140.mime_type) == 0)
			t140 = pt;
	}

	if (t140 && red) {
		int t140_payload_type_number = payload_type_get_number(t140);
		char *red_fmtp = ms_strdup_printf("%i/%i/%i", t140_payload_type_number, t140_payload_type_number, t140_payload_type_number);
		payload_type_set_recv_fmtp(red, red_fmtp);
		ms_free(red_fmtp);
	}
}

std::list<OrtpPayloadType*> PayloadTypeHandler::createSpecialPayloadTypes (const std::list<OrtpPayloadType*> & codecs) {
	std::list<OrtpPayloadType*> result;
	
	if (linphone_core_get_use_rfc2833_for_dtmf(getCore()->getCCore())){
		result = createTelephoneEventPayloadTypes(codecs);
	}
	if (linphone_core_generic_comfort_noise_enabled(getCore()->getCCore())) {
		OrtpPayloadType *cn = payload_type_clone(&payload_type_cn);
		payload_type_set_number(cn, 13);
		result.push_back(cn);
	}
	return result;
}

std::list<OrtpPayloadType*> PayloadTypeHandler::createTelephoneEventPayloadTypes (const std::list<OrtpPayloadType*> & codecs) {
	std::list<OrtpPayloadType*> result;
	for (const auto & pt : codecs) {
		if (hasTelephoneEventPayloadType(result, pt->clock_rate))
			continue;

		OrtpPayloadType *tev = payload_type_clone(&payload_type_telephone_event);
		tev->clock_rate = pt->clock_rate;
		// Let it choose the number dynamically as for normal codecs.
		payload_type_set_number(tev, -1);
		// But for first telephone-event, prefer the number that was configured in the core.
		if (!result.empty() && isPayloadTypeNumberAvailable(codecs, getCore()->getCCore()->codecs_conf.telephone_event_pt, nullptr))
			payload_type_set_number(tev, getCore()->getCCore()->codecs_conf.telephone_event_pt);
		result.push_back(tev);
	}
	return result;
}

bool PayloadTypeHandler::isPayloadTypeUsable (const OrtpPayloadType *pt) {
	return isPayloadTypeUsableForBandwidth(
		pt, getMinBandwidth(
			linphone_core_get_download_bandwidth(getCore()->getCCore()),
			linphone_core_get_upload_bandwidth(getCore()->getCCore())
		)
	);
}

// -----------------------------------------------------------------------------

bool PayloadTypeHandler::bandwidthIsGreater (int bandwidth1, int bandwidth2) {
	if (bandwidth1 <= 0) return true;
	if (bandwidth2 <= 0) return false;
	return bandwidth1 >= bandwidth2;
}

int PayloadTypeHandler::getAudioPayloadTypeBandwidth (const OrtpPayloadType *pt, int maxBandwidth) {
	if (payload_type_is_vbr(pt)) {
		if (pt->flags & PAYLOAD_TYPE_BITRATE_OVERRIDE) {
			lDebug() << "PayloadType " << pt->mime_type << "/" << pt->clock_rate << " has bitrate override";
			return pt->normal_bitrate / 1000;
		}
		return lookupTypicalVbrBitrate(maxBandwidth, pt->clock_rate);
	}
	/* Rounding codec bandwidth should be avoid, specially for AMR */
	return (int)ceil(getAudioPayloadTypeBandwidthFromCodecBitrate(pt) / 1000.0);
}

int PayloadTypeHandler::getVideoPayloadTypeBandwidth (const OrtpPayloadType *pt, int maxBandwidth) {
	if (pt->flags & PAYLOAD_TYPE_BITRATE_OVERRIDE) {
		lDebug() << "PayloadType " << pt->mime_type << "/" << pt->clock_rate << " has bitrate override";
		return getMinBandwidth(maxBandwidth, pt->normal_bitrate / 1000);
	}else
		return maxBandwidth;
}

/*
 *((codec-birate*ptime/8) + RTP header + UDP header + IP header)*8/ptime;
 * ptime=1/npacket
 */
double PayloadTypeHandler::getAudioPayloadTypeBandwidthFromCodecBitrate (const OrtpPayloadType *pt) {
	double npacket = 50;
	if (strcmp(payload_type_get_mime(&payload_type_aaceld_44k), payload_type_get_mime(pt)) == 0)
		// Special case of aac 44K because ptime=10ms.
		npacket = 100;
	else if (strcmp(payload_type_get_mime(&payload_type_ilbc), payload_type_get_mime(pt)) == 0)
		npacket = 1000 / 30.0;

	int bitrate = pt->normal_bitrate;
	double packet_size = (((double)bitrate) / (npacket * 8)) + udpHeaderSize + rtpHeaderSize + ipv4HeaderSize;
	return packet_size * 8.0 * npacket;
}

int PayloadTypeHandler::getMaxCodecSampleRate (const std::list<OrtpPayloadType*> & codecs) {
	int maxSampleRate = 0;
	for (const auto & pt : codecs) {
		int sampleRate;
		if (strcasecmp("G722", pt->mime_type) == 0)
			// G722 spec says 8000 but the codec actually requires 16000.
			sampleRate = 16000;
		else
			sampleRate = pt->clock_rate;

		if (sampleRate > maxSampleRate)
			maxSampleRate = sampleRate;
	}
	return maxSampleRate;
}

int PayloadTypeHandler::getMinBandwidth (int downBandwidth, int upBandwidth) {
	if (downBandwidth <= 0) return upBandwidth;
	if (upBandwidth <= 0) return downBandwidth;
	return MIN(downBandwidth, upBandwidth);
}

int PayloadTypeHandler::getRemainingBandwidthForVideo (int total, int audio) {
	int remaining = total - audio - 10;
	if (remaining < 0) remaining = 0;
	return remaining;
}

bool PayloadTypeHandler::isPayloadTypeNumberAvailable (const std::list<OrtpPayloadType*> & codecs, int number, const OrtpPayloadType *ignore) {
	for (const auto & pt : codecs) {
		if ((pt != ignore) && (payload_type_get_number(pt) == number)) return false;
	}
	return true;
}

// -----------------------------------------------------------------------------

std::list<OrtpPayloadType*> PayloadTypeHandler::makeCodecsList (SalStreamType type, int bandwidthLimit, int maxCodecs, const std::list<OrtpPayloadType*> & previousList) {
	const bctbx_list_t *allCodecs = nullptr;
	switch (type) {
		default:
		case SalAudio:
			allCodecs = getCore()->getCCore()->codecs_conf.audio_codecs;
			break;
		case SalVideo:
			allCodecs = getCore()->getCCore()->codecs_conf.video_codecs;
			break;
		case SalText:
			allCodecs = getCore()->getCCore()->codecs_conf.text_codecs;
			break;
	}

	int nb = 0;
	std::list<OrtpPayloadType*> result;
	for (const bctbx_list_t *it = allCodecs; it != nullptr; it = bctbx_list_next(it)) {
		OrtpPayloadType *pt = reinterpret_cast<OrtpPayloadType *>(bctbx_list_get_data(it));
		if (!payload_type_enabled(pt))
			continue;

		if (bandwidthLimit > 0 && !isPayloadTypeUsableForBandwidth(pt, bandwidthLimit)) {
			lInfo() << "Codec " << pt->mime_type << "/" << pt->clock_rate << " eliminated because of audio bandwidth constraint of " <<
				bandwidthLimit << " kbit/s";
			continue;
		}

		if (!isPayloadTypeUsable(pt)) {
			lInfo() << "Codec " << pt->mime_type << "/" << pt->clock_rate << "is unusable";
			continue;
		}

		auto clonedPt = payload_type_clone(pt);

		/* Look for a previously assigned number for this codec */
		int num = findPayloadTypeNumber(previousList, clonedPt);
		if (num != -1) {
			payload_type_set_number(clonedPt, num);
			payload_type_set_flag(clonedPt, PAYLOAD_TYPE_FROZEN_NUMBER);
		}

		result.push_back(clonedPt);
		nb++;
		if ((maxCodecs > 0) && (nb >= maxCodecs)) break;
	}
	if (type == SalAudio) {
		auto specials = createSpecialPayloadTypes(result);
		result.insert(result.cend(), specials.cbegin(), specials.cend());
	}
	assignPayloadTypeNumbers(result);
	return result;
}

void PayloadTypeHandler::clearPayloadList(std::list<OrtpPayloadType*> & payloads) {
	for (auto & pt : payloads) {
		payload_type_destroy(pt);
	}
	payloads.clear();
}

LINPHONE_END_NAMESPACE
