/*
 * 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 <bctoolbox/defs.h>

#include "linphone/api/c-content.h"
#include "linphone/core.h"

#include "address/address-p.h"
#include "c-wrapper/c-wrapper.h"
#include "call/call-p.h"
#include "conference/params/call-session-params-p.h"
#include "conference/session/call-session-p.h"
#include "conference/session/call-session.h"
#include "core/core-p.h"
#include "logger/logger.h"

#include "private.h"

using namespace std;

LINPHONE_BEGIN_NAMESPACE

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

int CallSessionPrivate::computeDuration () const {
	if (log->connected_date_time == 0)
		return 0;
	return (int)(ms_time(nullptr) - log->connected_date_time);
}

/*
 * Initialize call parameters according to incoming call parameters. This is to avoid to ask later (during reINVITEs) for features that the remote
 * end apparently does not support. This features are: privacy, video...
 */
void CallSessionPrivate::initializeParamsAccordingToIncomingCallParams () {
	currentParams->setPrivacy((LinphonePrivacyMask)op->getPrivacy());
}

void CallSessionPrivate::notifyReferState () {
	SalCallOp *refererOp = referer->getPrivate()->getOp();
	if (refererOp)
		refererOp->notifyReferState(op);
}

void CallSessionPrivate::setState (CallSession::State newState, const string &message) {
	L_Q();
	// Keep a ref on the CallSession, otherwise it might get destroyed before the end of the method
	shared_ptr<CallSession> ref = q->getSharedFromThis();
	if (state != newState){
		prevState = state;

		// Make sanity checks with call state changes. Any bad transition can result in unpredictable results
		// or irrecoverable errors in the application.
		if ((state == CallSession::State::End) || (state == CallSession::State::Error)) {
			if (newState != CallSession::State::Released) {
				lFatal() << "Abnormal call resurection from " << Utils::toString(state) <<
					" to " << Utils::toString(newState) << " , aborting";
				return;
			}
		} else if ((newState == CallSession::State::Released) && (prevState != CallSession::State::Error) && (prevState != CallSession::State::End)) {
			lFatal() << "Attempt to move CallSession [" << q << "] to Released state while it was not previously in Error or End state, aborting";
			return;
		}
		lInfo() << "CallSession [" << q << "] moving from state " << Utils::toString(state) << " to " << Utils::toString(newState);

		if (newState != CallSession::State::Referred) {
			// CallSession::State::Referred is rather an event, not a state.
			// Indeed it does not change the state of the call (still paused or running).
			state = newState;
		}

		switch (newState) {
			case CallSession::State::End:
			case CallSession::State::Error:
				switch (linphone_error_info_get_reason(q->getErrorInfo())) {
					case LinphoneReasonDeclined:
						if (log->status != LinphoneCallMissed) // Do not re-change the status of a call if it's already set
							log->status = LinphoneCallDeclined;
						break;
					case LinphoneReasonNotAnswered:
						if (log->dir == LinphoneCallIncoming)
							log->status = LinphoneCallMissed;
						break;
					case LinphoneReasonNone:
						if (log->dir == LinphoneCallIncoming) {
							if (ei) {
								int code = linphone_error_info_get_protocol_code(ei);
								if ((code >= 200) && (code < 300))
									log->status = LinphoneCallAcceptedElsewhere;
								else if (code == 487)
									log->status = LinphoneCallMissed;
							}
						}
						break;
					case LinphoneReasonDoNotDisturb:
						if (log->dir == LinphoneCallIncoming) {
							if (ei) {
								int code = linphone_error_info_get_protocol_code(ei);
								if ((code >= 600) && (code < 700))
									log->status = LinphoneCallDeclinedElsewhere;
							}
						}
						break;
					default:
						break;
				}
				setTerminated();
				break;
			case CallSession::State::Connected:
				log->status = LinphoneCallSuccess;
				log->connected_date_time = ms_time(nullptr);
				break;
			default:
				break;
		}

		if (message.empty()) {
			lError() << "You must fill a reason when changing call state (from " <<
				Utils::toString(prevState) << " to " << Utils::toString(state) << ")";
		}
		if (listener)
			listener->onCallSessionStateChanged(q->getSharedFromThis(), newState, message);
		if (newState == CallSession::State::Released)
			setReleased(); /* Shall be performed after app notification */
	}
}

void CallSessionPrivate::setTransferState (CallSession::State newState) {
	L_Q();
	if (newState == transferState)
		return;
	lInfo() << "Transfer state for CallSession [" << q << "] changed from ["
		<< Utils::toString(transferState) << "] to [" << Utils::toString(newState) << "]";
	transferState = newState;
	if (listener)
		listener->onCallSessionTransferStateChanged(q->getSharedFromThis(), newState);
}

void CallSessionPrivate::startIncomingNotification () {
	L_Q();
	if (listener)
		listener->onIncomingCallSessionStarted(q->getSharedFromThis());

	setState(CallSession::State::IncomingReceived, "Incoming CallSession");

	// From now on, the application is aware of the call and supposed to take background task or already submitted
	// notification to the user. We can then drop our background task.
	if (listener)
		listener->onBackgroundTaskToBeStopped(q->getSharedFromThis());

	if (state == CallSession::State::IncomingReceived) {
		handleIncomingReceivedStateInIncomingNotification();
	}
}

bool CallSessionPrivate::startPing () {
	L_Q();
	if (q->getCore()->getCCore()->sip_conf.ping_with_options) {
		/* Defer the start of the call after the OPTIONS ping for outgoing call or
		 * send an option request back to the caller so that we get a chance to discover our nat'd address
		 * before answering for incoming call */
		pingReplied = false;
		pingOp = new SalOp(q->getCore()->getCCore()->sal);
		if (direction == LinphoneCallIncoming) {
			string from = pingOp->getFrom();
			string to = pingOp->getTo();
			linphone_configure_op(q->getCore()->getCCore(), pingOp, log->from, nullptr, false);
			pingOp->setRoute(op->getNetworkOrigin());
			pingOp->ping(from.c_str(), to.c_str());
		} else if (direction == LinphoneCallOutgoing) {
			char *from = linphone_address_as_string(log->from);
			char *to = linphone_address_as_string(log->to);
			pingOp->ping(from, to);
			ms_free(from);
			ms_free(to);
		}
		pingOp->setUserPointer(this);
		return true;
	}
	return false;
}

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

void CallSessionPrivate::setParams (CallSessionParams *csp) {
	if (params)
		delete params;
	params = csp;
}

void CallSessionPrivate::createOp () {
	createOpTo(log->to);
}

bool CallSessionPrivate::isInConference () const {
	return params->getPrivate()->getInConference();
}

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

void CallSessionPrivate::abort (const string &errorMsg) {
	op->terminate();
	setState(CallSession::State::Error, errorMsg);
}

void CallSessionPrivate::accepted () {
	/* Immediately notify the connected state, even if errors occur after */
	switch (state) {
		case CallSession::State::OutgoingProgress:
		case CallSession::State::OutgoingRinging:
		case CallSession::State::OutgoingEarlyMedia:
			/* Immediately notify the connected state */
			setState(CallSession::State::Connected, "Connected");
			break;
		default:
			break;
	}
	currentParams->setPrivacy((LinphonePrivacyMask)op->getPrivacy());
}

void CallSessionPrivate::ackBeingSent (LinphoneHeaders *headers) {
	L_Q();
	if (listener)
		listener->onAckBeingSent(q->getSharedFromThis(), headers);
}

void CallSessionPrivate::ackReceived (LinphoneHeaders *headers) {
	L_Q();
	if (listener)
		listener->onAckReceived(q->getSharedFromThis(), headers);
}

void CallSessionPrivate::cancelDone () {
	if (reinviteOnCancelResponseRequested) {
		reinviteOnCancelResponseRequested = false;
		reinviteToRecoverFromConnectionLoss();
	}
}

bool CallSessionPrivate::failure () {
	L_Q();
	const SalErrorInfo *ei = op->getErrorInfo();
	switch (ei->reason) {
		case SalReasonRedirect:
			if ((state == CallSession::State::OutgoingInit) || (state == CallSession::State::OutgoingProgress)
				|| (state == CallSession::State::OutgoingRinging) /* Push notification case */ || (state == CallSession::State::OutgoingEarlyMedia)) {
				const SalAddress *redirectionTo = op->getRemoteContactAddress();
				if (redirectionTo) {
					char *url = sal_address_as_string(redirectionTo);
					lWarning() << "Redirecting CallSession [" << q << "] to " << url;
					if (log->to)
						linphone_address_unref(log->to);
					log->to = linphone_address_new(url);
					ms_free(url);
					restartInvite();
					return true;
				}
			}
			break;
		default:
			break;
	}

	/* Some call errors are not fatal */
	switch (state) {
		case CallSession::State::Updating:
		case CallSession::State::Pausing:
		case CallSession::State::Resuming:
			if (ei->reason != SalReasonNoMatch) {
				lInfo() << "Call error on state [" << Utils::toString(state) << "], restoring previous state [" << Utils::toString(prevState) << "]";
				setState(prevState, ei->full_string);
				return true;
			}
		default:
			break;
	}

	if ((state != CallSession::State::End) && (state != CallSession::State::Error)) {
		if (ei->reason == SalReasonDeclined)
			setState(CallSession::State::End, "Call declined");
		else {
			if (CallSession::isEarlyState(state))
				setState(CallSession::State::Error, ei->full_string ? ei->full_string : "");
			else
				setState(CallSession::State::End, ei->full_string ? ei->full_string : "");
		}
	}
	if (referer) {
		// Notify referer of the failure
		notifyReferState();
	}
	return false;
}

void CallSessionPrivate::infoReceived (SalBodyHandler *bodyHandler) {
	L_Q();
	LinphoneInfoMessage *info = linphone_core_create_info_message(q->getCore()->getCCore());
	linphone_info_message_set_headers(info, op->getRecvCustomHeaders());
	if (bodyHandler) {
		LinphoneContent *content = linphone_content_from_sal_body_handler(bodyHandler);
		linphone_info_message_set_content(info, content);
		linphone_content_unref(content);
	}
	if (listener)
		listener->onInfoReceived(q->getSharedFromThis(), info);
	linphone_info_message_unref(info);
}

void CallSessionPrivate::pingReply () {
	L_Q();
	if (state == CallSession::State::OutgoingInit) {
		pingReplied = true;
		if (isReadyForInvite())
			q->startInvite(nullptr, "");
	}
}

void CallSessionPrivate::referred (const Address &referToAddr) {
	L_Q();
	referTo = referToAddr.asString();
	referPending = true;
	setState(CallSession::State::Referred, "Referred");
	if (referPending && listener)
		listener->onCallSessionStartReferred(q->getSharedFromThis());
}

void CallSessionPrivate::remoteRinging () {
	/* Set privacy */
	currentParams->setPrivacy((LinphonePrivacyMask)op->getPrivacy());
	setState(CallSession::State::OutgoingRinging, "Remote ringing");
}

void CallSessionPrivate::replaceOp (SalCallOp *newOp) {
	L_Q();
	SalCallOp *oldOp = op;
	CallSession::State oldState = state;
	op = newOp;
	op->setUserPointer(q);
	op->setLocalMediaDescription(oldOp->getLocalMediaDescription());
	switch (state) {
		case CallSession::State::IncomingEarlyMedia:
		case CallSession::State::IncomingReceived:
			op->notifyRinging((state == CallSession::State::IncomingEarlyMedia) ? true : false);
			break;
		case CallSession::State::Connected:
		case CallSession::State::StreamsRunning:
			op->accept();
			break;
		default:
			lWarning() << "CallSessionPrivate::replaceOp(): don't know what to do in state [" << Utils::toString(state) << "]";
			break;
	}
	switch (oldState) {
		case CallSession::State::IncomingEarlyMedia:
		case CallSession::State::IncomingReceived:
			oldOp->setUserPointer(nullptr); // In order for the call session to not get terminated by terminating this op
			// Do not terminate a forked INVITE
			lInfo() << "CallSessionPrivate::replaceOp(): terminating old session in early state.";
			if (op->getReplaces()){
				oldOp->terminate();
			}else{
				oldOp->killDialog();
			}
			break;
		case CallSession::State::Connected:
		case CallSession::State::StreamsRunning:
			lInfo() << "CallSessionPrivate::replaceOp(): terminating old session in running state.";
			oldOp->terminate();
			oldOp->killDialog();
			break;
		default:
			break;
	}
	oldOp->release();
}

void CallSessionPrivate::terminated () {
	L_Q();
	switch (state) {
		case CallSession::State::End:
		case CallSession::State::Error:
			lWarning() << "terminated: already terminated, ignoring";
			return;
		case CallSession::State::IncomingReceived:
		case CallSession::State::IncomingEarlyMedia:
			if (!op->getReasonErrorInfo()->protocol || strcmp(op->getReasonErrorInfo()->protocol, "") == 0) {
				linphone_error_info_set(ei, nullptr, LinphoneReasonNotAnswered, 0, "Incoming call cancelled", nullptr);
				nonOpError = true;
			}
			break;
		default:
			break;
	}
	if (referPending && listener)
		listener->onCallSessionStartReferred(q->getSharedFromThis());
	setState(CallSession::State::End, "Call ended");
}

void CallSessionPrivate::updated (bool isUpdate) {
	L_Q();
	deferUpdate = !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sip", "defer_update_default", FALSE);
	SalErrorInfo sei;
	memset(&sei, 0, sizeof(sei));
	switch (state) {
		case CallSession::State::PausedByRemote:
			updatedByRemote();
			break;
		/* SIP UPDATE CASE */
		case CallSession::State::OutgoingRinging:
		case CallSession::State::OutgoingEarlyMedia:
		case CallSession::State::IncomingEarlyMedia:
			if (isUpdate) {
				setState(CallSession::State::EarlyUpdatedByRemote, "EarlyUpdatedByRemote");
				acceptUpdate(nullptr, prevState, Utils::toString(prevState));
			}
			break;
		case CallSession::State::StreamsRunning:
		case CallSession::State::Connected:
		case CallSession::State::UpdatedByRemote: /* Can happen on UAC connectivity loss */
			updatedByRemote();
			break;
		case CallSession::State::Paused:
			/* We'll remain in pause state but accept the offer anyway according to default parameters */
			acceptUpdate(nullptr, state, Utils::toString(state));
			break;
		case CallSession::State::Updating:
		case CallSession::State::Pausing:
		case CallSession::State::Resuming:
			sal_error_info_set(&sei, SalReasonInternalError, "SIP", 0, nullptr, nullptr);
			op->declineWithErrorInfo(&sei, nullptr);
			BCTBX_NO_BREAK; /* no break */
		case CallSession::State::Idle:
		case CallSession::State::OutgoingInit:
		case CallSession::State::End:
		case CallSession::State::IncomingReceived:
		case CallSession::State::OutgoingProgress:
		case CallSession::State::Referred:
		case CallSession::State::Error:
		case CallSession::State::Released:
		case CallSession::State::EarlyUpdatedByRemote:
		case CallSession::State::EarlyUpdating:
			lWarning() << "Receiving reINVITE or UPDATE while in state [" << Utils::toString(state) << "], should not happen";
		break;
	}
}

void CallSessionPrivate::updatedByRemote () {
	L_Q();
	setState(CallSession::State::UpdatedByRemote,"Call updated by remote");
	if (deferUpdate || deferUpdateInternal) {
		if (state == CallSession::State::UpdatedByRemote && !deferUpdateInternal){
			lInfo() << "CallSession [" << q << "]: UpdatedByRemoted was signaled but defered. LinphoneCore expects the application to call linphone_call_accept_update() later";
		}
	} else {
		if (state == CallSession::State::UpdatedByRemote)
			q->acceptUpdate(nullptr);
		else {
			// Otherwise it means that the app responded by CallSession::acceptUpdate() within the callback,
			// so job is already done
		}
	}
}

void CallSessionPrivate::updating (bool isUpdate) {
	updated(isUpdate);
}

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

void CallSessionPrivate::init () {
	currentParams = new CallSessionParams();
	ei = linphone_error_info_new();
}

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

void CallSessionPrivate::accept (const CallSessionParams *csp) {
	/* Try to be best-effort in giving real local or routable contact address */
	setContactOp();
	if (csp)
		setParams(new CallSessionParams(*csp));
	if (params)
		op->setSentCustomHeaders(params->getPrivate()->getCustomHeaders());

	op->accept();
	setState(CallSession::State::Connected, "Connected");
}

void CallSessionPrivate::acceptOrTerminateReplacedSessionInIncomingNotification () {
	L_Q();
	CallSession *replacedSession = nullptr;
	if (lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sip", "auto_answer_replacing_calls", 1)) {
		if (op->getReplaces())
			replacedSession = reinterpret_cast<CallSession *>(op->getReplaces()->getUserPointer());
		if (replacedSession) {
			switch (replacedSession->getState()){
				/* If the replaced call is already accepted, then accept automatic replacement. */
				case CallSession::State::StreamsRunning:
				case CallSession::State::Connected:
				case CallSession::State::Paused:
				case CallSession::State::PausedByRemote:
				case CallSession::State::Pausing:
					lInfo() << " auto_answer_replacing_calls is true, replacing call is going to be accepted and replaced call terminated.";
					q->acceptDefault();
				break;
				default:
				break;
			}
		}
	}
}

LinphoneStatus CallSessionPrivate::acceptUpdate (const CallSessionParams *csp, CallSession::State nextState, const string &stateInfo) {
	return startAcceptUpdate(nextState, stateInfo);
}

LinphoneStatus CallSessionPrivate::checkForAcceptation () {
	L_Q();
	switch (state) {
		case CallSession::State::IncomingReceived:
		case CallSession::State::IncomingEarlyMedia:
			break;
		default:
			lError() << "checkForAcceptation() CallSession [" << q << "] is in state [" << Utils::toString(state) << "], operation not permitted";
			return -1;
	}
	if (listener)
		listener->onCheckForAcceptation(q->getSharedFromThis());

	/* Check if this call is supposed to replace an already running one */
	SalOp *replaced = op->getReplaces();
	if (replaced) {
		CallSession *session = reinterpret_cast<CallSession *>(replaced->getUserPointer());
		if (session) {
			lInfo() << "CallSession " << q << " replaces CallSession " << session << ". This last one is going to be terminated automatically";
			session->terminate();
		}
	}
	return 0;
}

void CallSessionPrivate::handleIncomingReceivedStateInIncomingNotification () {
	/* Try to be best-effort in giving real local or routable contact address for 100Rel case */
	setContactOp();
	if (notifyRinging)
		op->notifyRinging(false);
	acceptOrTerminateReplacedSessionInIncomingNotification();
}

bool CallSessionPrivate::isReadyForInvite () const {
	bool pingReady = false;
	if (pingOp) {
		if (pingReplied)
			pingReady = true;
	} else
		pingReady = true;
	return pingReady;
}

bool CallSessionPrivate::isUpdateAllowed (CallSession::State &nextState) const {
	switch (state) {
		case CallSession::State::IncomingReceived:
		case CallSession::State::IncomingEarlyMedia:
		case CallSession::State::OutgoingRinging:
		case CallSession::State::OutgoingEarlyMedia:
			nextState = CallSession::State::EarlyUpdating;
			break;
		case CallSession::State::Connected:
		case CallSession::State::StreamsRunning:
		case CallSession::State::PausedByRemote:
		case CallSession::State::UpdatedByRemote:
			nextState = CallSession::State::Updating;
			break;
		case CallSession::State::Paused:
			nextState = CallSession::State::Pausing;
			break;
		case CallSession::State::OutgoingProgress:
		case CallSession::State::Pausing:
		case CallSession::State::Resuming:
		case CallSession::State::Updating:
			nextState = state;
			break;
		default:
			lError() << "Update is not allowed in [" << Utils::toString(state) << "] state";
			return false;
	}
	return true;
}

int CallSessionPrivate::restartInvite () {
	L_Q();
	createOp();
	return q->startInvite(nullptr, subject);
}

/*
 * Called internally when reaching the Released state, to perform cleanups to break circular references.
**/
void CallSessionPrivate::setReleased () {
	L_Q();
	if (op) {
		/* Transfer the last error so that it can be obtained even in Released state */
		if (!nonOpError)
			linphone_error_info_from_sal_op(ei, op);
		/* So that we cannot have anymore upcalls for SAL concerning this call */
		op->release();
		op = nullptr;
	}
	referer = nullptr;
	transferTarget = nullptr;

	q->getCore()->getPrivate()->getToneManager()->removeSession(q->getSharedFromThis());

	if (listener)
		listener->onCallSessionSetReleased(q->getSharedFromThis());
}

/* This method is called internally to get rid of a call that was notified to the application,
 * because it reached the end or error state. It performs the following tasks:
 * - remove the call from the internal list of calls
 * - update the call logs accordingly
 */
void CallSessionPrivate::setTerminated() {
	L_Q();
	completeLog();
	if (listener)
		listener->onCallSessionSetTerminated(q->getSharedFromThis());
}

LinphoneStatus CallSessionPrivate::startAcceptUpdate (CallSession::State nextState, const std::string &stateInfo) {
	op->accept();
	setState(nextState, stateInfo);
	return 0;
}

LinphoneStatus CallSessionPrivate::startUpdate (const string &subject) {
	L_Q();
	string newSubject(subject);
	if (newSubject.empty()) {
		if (q->getParams()->getPrivate()->getInConference())
			newSubject = "Conference";
		else if (q->getParams()->getPrivate()->getInternalCallUpdate())
			newSubject = "ICE processing concluded";
		else if (q->getParams()->getPrivate()->getNoUserConsent())
			newSubject = "Refreshing";
		else
			newSubject = "Media change";
	}
	if (destProxy && destProxy->op) {
		/* Give a chance to update the contact address if connectivity has changed */
		op->setContactAddress(destProxy->op->getContactAddress());
	} else
		op->setContactAddress(nullptr);
	return op->update(newSubject.c_str(), q->getParams()->getPrivate()->getNoUserConsent());
}

void CallSessionPrivate::terminate () {
	if ((state == CallSession::State::IncomingReceived) && (linphone_error_info_get_reason(ei) != LinphoneReasonNotAnswered)) {
		linphone_error_info_set_reason(ei, LinphoneReasonDeclined);
		nonOpError = true;
	}
	setState(CallSession::State::End, "Call terminated");
}

void CallSessionPrivate::updateCurrentParams () const {}

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

void CallSessionPrivate::setBroken () {
	switch (state) {
		// For all the early states, we prefer to drop the call
		case CallSession::State::OutgoingInit:
		case CallSession::State::OutgoingProgress:
		case CallSession::State::OutgoingRinging:
		case CallSession::State::OutgoingEarlyMedia:
		case CallSession::State::IncomingReceived:
		case CallSession::State::IncomingEarlyMedia:
			// During the early states, the SAL layer reports the failure from the dialog or transaction layer,
			// hence, there is nothing special to do
		case CallSession::State::StreamsRunning:
		case CallSession::State::Updating:
		case CallSession::State::Pausing:
		case CallSession::State::Resuming:
		case CallSession::State::Paused:
		case CallSession::State::PausedByRemote:
		case CallSession::State::UpdatedByRemote:
			// During these states, the dialog is established. A failure of a transaction is not expected to close it.
			// Instead we have to repair the dialog by sending a reINVITE
			broken = true;
			needLocalIpRefresh = true;
			break;
		default:
			lError() << "CallSessionPrivate::setBroken(): unimplemented case";
			break;
	}
}

void CallSessionPrivate::setContactOp () {
	L_Q();
	SalAddress *salAddress = nullptr;
	LinphoneAddress *contact = getFixedContact();
	if (contact) {
		auto contactParams = q->getParams()->getPrivate()->getCustomContactParameters();
		for (auto it = contactParams.begin(); it != contactParams.end(); it++)
			linphone_address_set_param(contact, it->first.c_str(), it->second.empty() ? nullptr : it->second.c_str());
		salAddress = const_cast<SalAddress *>(L_GET_PRIVATE_FROM_C_OBJECT(contact)->getInternalAddress());
		op->setContactAddress(salAddress);
		linphone_address_unref(contact);
	}
}

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

void CallSessionPrivate::onNetworkReachable (bool sipNetworkReachable, bool mediaNetworkReachable) {
	if (sipNetworkReachable)
		repairIfBroken();
	else
		setBroken();
}

void CallSessionPrivate::onRegistrationStateChanged (LinphoneProxyConfig *cfg, LinphoneRegistrationState cstate, const std::string &message) {
	repairIfBroken();
}

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

void CallSessionPrivate::completeLog () {
	L_Q();
	log->duration = computeDuration(); /* Store duration since connected */
	log->error_info = linphone_error_info_ref(ei);
	if (log->status == LinphoneCallMissed)
		q->getCore()->getCCore()->missed_calls++;
	linphone_core_report_call_log(q->getCore()->getCCore(), log);
}

void CallSessionPrivate::createOpTo (const LinphoneAddress *to) {
	L_Q();
	if (op)
		op->release();
	op = new SalCallOp(q->getCore()->getCCore()->sal);
	op->setUserPointer(q);
	if (params->getPrivate()->getReferer())
		op->setReferrer(params->getPrivate()->getReferer()->getPrivate()->getOp());
	linphone_configure_op(q->getCore()->getCCore(), op, to, q->getParams()->getPrivate()->getCustomHeaders(), false);
	if (q->getParams()->getPrivacy() != LinphonePrivacyDefault)
		op->setPrivacy((SalPrivacyMask)q->getParams()->getPrivacy());
	/* else privacy might be set by proxy */
}

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

LinphoneAddress * CallSessionPrivate::getFixedContact () const {
	L_Q();
	LinphoneAddress *result = nullptr;
	if (op && op->getContactAddress()) {
		/* If already choosed, don't change it */
		return nullptr;
	} else if (pingOp && pingOp->getContactAddress()) {
		/* If the ping OPTIONS request succeeded use the contact guessed from the received, rport */
		lInfo() << "Contact has been fixed using OPTIONS";
		char *addr = sal_address_as_string(pingOp->getContactAddress());
		result = linphone_address_new(addr);
		ms_free(addr);
		return result;
	} else if (destProxy){
		const LinphoneAddress *addr = linphone_proxy_config_get_contact(destProxy);
		if (addr && (destProxy->op || destProxy->dependency != nullptr)) {
			/* If using a proxy, use the contact address as guessed with the REGISTERs */
			lInfo() << "Contact has been fixed using proxy";
			result = linphone_address_clone(addr);
			return result;
		}
	}
	result = linphone_core_get_primary_contact_parsed(q->getCore()->getCCore());
	if (result) {
		/* Otherwise use supplied localip */
		linphone_address_set_domain(result, nullptr /* localip */);
		linphone_address_set_port(result, -1 /* linphone_core_get_sip_port(core) */);
		lInfo() << "Contact has not been fixed, stack will do";
	}
	return result;
}

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

void CallSessionPrivate::reinviteToRecoverFromConnectionLoss () {
	L_Q();
	lInfo() << "CallSession [" << q << "] is going to be updated (reINVITE) in order to recover from lost connectivity";
	q->update(params);
}

void CallSessionPrivate::repairByInviteWithReplaces () {
	L_Q();
	lInfo() << "CallSession [" << q << "] is going to have a new INVITE replacing the previous one in order to recover from lost connectivity";
	string callId = op->getCallId();
	const char *fromTag = op->getLocalTag();
	const char *toTag = op->getRemoteTag();
	op->killDialog();
	createOp();
	op->setReplaces(callId.c_str(), fromTag, toTag);
	q->startInvite(nullptr);
}

void CallSessionPrivate::repairIfBroken () {
	L_Q();

	try {
		LinphoneCore *lc = q->getCore()->getCCore();
		LinphoneConfig *config = linphone_core_get_config(lc);
		if (!lp_config_get_int(config, "sip", "repair_broken_calls", 1) || !lc->media_network_state.global_state || !broken)
			return;
	} catch (const bad_weak_ptr &) {
		return; // Cannot repair if core is destroyed.
	}

	// If we are registered and this session has been broken due to a past network disconnection,
	// attempt to repair it

	// Make sure that the proxy from which we received this call, or to which we routed this call is registered first
	if (destProxy) {
		// In all other cases, ie no proxy config, or a proxy config for which no registration was requested,
		// we can start the call session repair immediately.
		if (linphone_proxy_config_register_enabled(destProxy)
			&& (linphone_proxy_config_get_state(destProxy) != LinphoneRegistrationOk))
			return;
	}

	SalErrorInfo sei;
	memset(&sei, 0, sizeof(sei));
	switch (state) {
		case CallSession::State::Updating:
		case CallSession::State::Pausing:
			if (op->dialogRequestPending()) {
				// Need to cancel first re-INVITE as described in section 5.5 of RFC 6141
				if (op->cancelInvite() == 0){
					reinviteOnCancelResponseRequested = true;
				}
			}
			break;
		case CallSession::State::StreamsRunning:
		case CallSession::State::Paused:
		case CallSession::State::PausedByRemote:
			if (!op->dialogRequestPending())
				reinviteToRecoverFromConnectionLoss();
			break;
		case CallSession::State::UpdatedByRemote:
			if (op->dialogRequestPending()) {
				sal_error_info_set(&sei, SalReasonServiceUnavailable, "SIP", 0, nullptr, nullptr);
				op->declineWithErrorInfo(&sei, nullptr);
			}
			reinviteToRecoverFromConnectionLoss();
			break;
		case CallSession::State::OutgoingInit:
		case CallSession::State::OutgoingProgress:
			if (op->cancelInvite() == 0){
				reinviteOnCancelResponseRequested = true;
			}
			break;
		case CallSession::State::OutgoingEarlyMedia:
		case CallSession::State::OutgoingRinging:
			if (op->getRemoteTag() != nullptr){
				repairByInviteWithReplaces();
			}else{
				lWarning() << "No remote tag in last provisional response, no early dialog, so trying to cancel lost INVITE and will retry later.";
				if (op->cancelInvite() == 0){
					reinviteOnCancelResponseRequested = true;
				}
			}
			break;
		case CallSession::State::IncomingEarlyMedia:
		case CallSession::State::IncomingReceived:
			// Keep the call broken until a forked INVITE is received from the server
			break;
		default:
			lWarning() << "CallSessionPrivate::repairIfBroken: don't know what to do in state [" << Utils::toString(state);
			broken = false;
			break;
	}
	sal_error_info_reset(&sei);
}

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

CallSession::CallSession (const shared_ptr<Core> &core, const CallSessionParams *params, CallSessionListener *listener)
	: Object(*new CallSessionPrivate), CoreAccessor(core) {
	L_D();
	getCore()->getPrivate()->registerListener(d);
	d->listener = listener;
	if (params)
		d->setParams(new CallSessionParams(*params));
	d->init();
	lInfo() << "New CallSession [" << this << "] initialized (LinphoneCore version: " << linphone_core_get_version() << ")";
}

CallSession::CallSession (CallSessionPrivate &p, const shared_ptr<Core> &core) : Object(p), CoreAccessor(core) {
	L_D();
	getCore()->getPrivate()->registerListener(d);
	d->init();
}

CallSession::~CallSession () {
	L_D();
	try { //getCore may no longuer be available when deleting, specially in case of managed enviroment like java
		getCore()->getPrivate()->unregisterListener(d);
	} catch (const bad_weak_ptr &) {}
	if (d->currentParams)
		delete d->currentParams;
	if (d->params)
		delete d->params;
	if (d->remoteParams)
		delete d->remoteParams;
	if (d->ei)
		linphone_error_info_unref(d->ei);
	if (d->log)
		linphone_call_log_unref(d->log);
	if (d->op)
		d->op->release();
}

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

void CallSession::setListener(CallSessionListener *listener){
	L_D();
	d->listener = listener;
}


void CallSession::acceptDefault(){
	accept();
}

LinphoneStatus CallSession::accept (const CallSessionParams *csp) {
	L_D();
	LinphoneStatus result = d->checkForAcceptation();
	if (result < 0) return result;
	d->accept(csp);
	return 0;
}

LinphoneStatus CallSession::acceptUpdate (const CallSessionParams *csp) {
	L_D();
	if (d->state != CallSession::State::UpdatedByRemote) {
		lError() << "CallSession::acceptUpdate(): invalid state " << Utils::toString(d->state) << " to call this method";
		return -1;
	}
	return d->acceptUpdate(csp, d->prevState, Utils::toString(d->prevState));
}

void CallSession::configure (LinphoneCallDir direction, LinphoneProxyConfig *cfg, SalCallOp *op, const Address &from, const Address &to) {
	L_D();
	d->direction = direction;
	d->destProxy = cfg;
	LinphoneAddress *fromAddr = linphone_address_new(from.asString().c_str());
	LinphoneAddress *toAddr = linphone_address_new(to.asString().c_str());
	if (!d->destProxy) {
		/* Try to define the destination proxy if it has not already been done to have a correct contact field in the SIP messages */
		d->destProxy = linphone_core_lookup_known_proxy(getCore()->getCCore(), toAddr);
	}
	d->log = linphone_call_log_new(direction, fromAddr, toAddr);

	if (op) {
		/* We already have an op for incoming calls */
		d->op = op;
		d->op->setUserPointer(this);
		op->enableCnxIpTo0000IfSendOnly(
			!!lp_config_get_default_int(
				linphone_core_get_config(getCore()->getCCore()), "sip", "cnx_ip_to_0000_if_sendonly_enabled", 0
			)
		);
		linphone_call_log_set_call_id(d->log, op->getCallId().c_str()); /* Must be known at that time */
	}

	if (direction == LinphoneCallOutgoing) {
		if (d->params->getPrivate()->getReferer())
			d->referer = d->params->getPrivate()->getReferer();
		d->startPing();
	} else if (direction == LinphoneCallIncoming) {
		d->setParams(new CallSessionParams());
		d->params->initDefault(getCore(), LinphoneCallIncoming);
	}
}

LinphoneStatus CallSession::decline (LinphoneReason reason) {
	LinphoneErrorInfo *ei = linphone_error_info_new();
	linphone_error_info_set(ei, "SIP", reason, linphone_reason_to_error_code(reason), nullptr, nullptr);
	LinphoneStatus status = decline(ei);
	linphone_error_info_unref(ei);
	return status;
}

LinphoneStatus CallSession::decline (const LinphoneErrorInfo *ei) {
	L_D();
	SalErrorInfo sei;
	SalErrorInfo sub_sei;
	memset(&sei, 0, sizeof(sei));
	memset(&sub_sei, 0, sizeof(sub_sei));
	sei.sub_sei = &sub_sei;
	if ((d->state != CallSession::State::IncomingReceived) && (d->state != CallSession::State::IncomingEarlyMedia)) {
		lError() << "Cannot decline a CallSession that is in state " << Utils::toString(d->state);
		return -1;
	}
	if (ei) {
		linphone_error_info_to_sal(ei, &sei);
		d->op->declineWithErrorInfo(&sei , nullptr);
	} else
		d->op->decline(SalReasonDeclined);
	sal_error_info_reset(&sei);
	sal_error_info_reset(&sub_sei);
	d->terminate();
	return 0;
}

LinphoneStatus CallSession::declineNotAnswered (LinphoneReason reason) {
	L_D();
	d->log->status = LinphoneCallMissed;
	d->nonOpError = true;
	linphone_error_info_set(d->ei, nullptr, reason, linphone_reason_to_error_code(reason), "Not answered", nullptr);
	return decline(reason);
}

LinphoneStatus CallSession::deferUpdate () {
	L_D();
	if (d->state != CallSession::State::UpdatedByRemote) {
		lError() << "CallSession::deferUpdate() not done in state CallSession::State::UpdatedByRemote";
		return -1;
	}
	d->deferUpdate = true;
	return 0;
}

bool CallSession::hasTransferPending () {
	L_D();
	return d->referPending;
}

void CallSession::initiateIncoming () {}

bool CallSession::initiateOutgoing () {
	L_D();
	bool defer = false;
	d->setState(CallSession::State::OutgoingInit, "Starting outgoing call");
	d->log->start_date_time = ms_time(nullptr);
	if (!d->destProxy)
		defer = d->startPing();
	return defer;
}

void CallSession::iterate (time_t currentRealTime, bool oneSecondElapsed) {
	L_D();
	int elapsed = (int)(currentRealTime - d->log->start_date_time);
	if ((d->state == CallSession::State::OutgoingInit) && (elapsed > getCore()->getCCore()->sip_conf.delayed_timeout)) {
		/* Start the call even if the OPTIONS reply did not arrive */
		startInvite(nullptr, "");
	}
	if ((d->state == CallSession::State::IncomingReceived) || (d->state == CallSession::State::IncomingEarlyMedia)) {
		if (d->listener)
			d->listener->onIncomingCallSessionTimeoutCheck(getSharedFromThis(), elapsed, oneSecondElapsed);
	}
	if ((getCore()->getCCore()->sip_conf.in_call_timeout > 0) && (d->log->connected_date_time != 0)
		&& ((currentRealTime - d->log->connected_date_time) > getCore()->getCCore()->sip_conf.in_call_timeout)) {
		lInfo() << "In call timeout (" << getCore()->getCCore()->sip_conf.in_call_timeout << ")";
		terminate();
	}
}

LinphoneStatus CallSession::redirect (const string &redirectUri) {
	Address address(getCore()->interpretUrl(redirectUri));
	if (!address.isValid()) {
		/* Bad url */
		lError() << "Bad redirect URI: " << redirectUri;
		return -1;
	}
	return redirect(address);
}

LinphoneStatus CallSession::redirect (const Address &redirectAddr) {
	L_D();
	if (d->state != CallSession::State::IncomingReceived) {
		lError() << "Bad state for CallSession redirection";
		return -1;
	}
	SalErrorInfo sei;
	memset(&sei, 0, sizeof(sei));
	sal_error_info_set(&sei, SalReasonRedirect, "SIP", 0, nullptr, nullptr);
	d->op->declineWithErrorInfo(&sei, redirectAddr.getPrivate()->getInternalAddress());
	linphone_error_info_set(d->ei, nullptr, LinphoneReasonMovedPermanently, 302, "Call redirected", nullptr);
	d->nonOpError = true;
	d->terminate();
	sal_error_info_reset(&sei);
	return 0;
}

void CallSession::startIncomingNotification (bool notifyRinging) {
	L_D();
	d->notifyRinging = notifyRinging;
	if (d->listener) {
		d->listener->onIncomingCallSessionNotified(getSharedFromThis());
		d->listener->onBackgroundTaskToBeStarted(getSharedFromThis());
	}
	/* Prevent the CallSession from being destroyed while we are notifying, if the user declines within the state callback */
	shared_ptr<CallSession> ref = getSharedFromThis();
	if (d->deferIncomingNotification) {
		lInfo() << "Defer incoming notification";
		return;
	}

	d->startIncomingNotification();
}

int CallSession::startInvite (const Address *destination, const string &subject, const Content *content) {
	L_D();
	d->subject = subject;
	/* Try to be best-effort in giving real local or routable contact address */
	d->setContactOp();
	string destinationStr;
	char *realUrl = nullptr;
	if (destination)
		destinationStr = destination->asString();
	else {
		realUrl = linphone_address_as_string(d->log->to);
		destinationStr = realUrl;
		ms_free(realUrl);
	}
	char *from = linphone_address_as_string(d->log->from);
	/* Take a ref because sal_call() may destroy the CallSession if no SIP transport is available */
	shared_ptr<CallSession> ref = getSharedFromThis();
	if (content)
		d->op->setLocalBody(*content);

	// If a custom Content has been set in the call params, create a multipart body for the INVITE
	for (auto& content : d->params->getCustomContents()) {
		d->op->addAdditionalLocalBody(content);
	}

	int result = d->op->call(from, destinationStr, subject);
	ms_free(from);
	if (result < 0) {
		if ((d->state != CallSession::State::Error) && (d->state != CallSession::State::Released)) {
			// sal_call() may invoke call_failure() and call_released() SAL callbacks synchronously,
			// in which case there is no need to perform a state change here.
			d->setState(CallSession::State::Error, "Call failed");
		}
	} else {
		linphone_call_log_set_call_id(d->log, d->op->getCallId().c_str()); /* Must be known at that time */
		d->setState(CallSession::State::OutgoingProgress, "Outgoing call in progress");
	}
	return result;
}

LinphoneStatus CallSession::terminate (const LinphoneErrorInfo *ei) {
	L_D();
	lInfo() << "Terminate CallSession [" << this << "] which is currently in state [" << Utils::toString(d->state) << "]";
	SalErrorInfo sei;
	memset(&sei, 0, sizeof(sei));
	switch (d->state) {
		case CallSession::State::Released:
		case CallSession::State::End:
		case CallSession::State::Error:
			lWarning() << "No need to terminate CallSession [" << this << "] in state [" << Utils::toString(d->state) << "]";
			return -1;
		case CallSession::State::IncomingReceived:
		case CallSession::State::IncomingEarlyMedia:
			return decline(ei);
		case CallSession::State::OutgoingInit:
			/* In state OutgoingInit, op has to be destroyed */
			d->op->release();
			d->op = nullptr;
			break;
		default:
			if (ei) {
				linphone_error_info_to_sal(ei, &sei);
				d->op->terminate(&sei);
				sal_error_info_reset(&sei);
			} else
				d->op->terminate();
			break;
	}

	d->terminate();
	return 0;
}

LinphoneStatus CallSession::transfer (const shared_ptr<CallSession> &dest) {
	L_D();
	int result = d->op->referWithReplaces(dest->getPrivate()->op);
	d->setTransferState(CallSession::State::OutgoingInit);
	return result;
}

LinphoneStatus CallSession::transfer (const string &dest) {
	L_D();
	Address address(getCore()->interpretUrl(dest));
	if (!address.isValid())
		return -1;
	d->op->refer(address.asString().c_str());
	d->setTransferState(CallSession::State::OutgoingInit);
	return 0;
}

LinphoneStatus CallSession::update (const CallSessionParams *csp, const string &subject, const Content *content) {
	L_D();
	CallSession::State nextState;
	CallSession::State initialState = d->state;
	if (!d->isUpdateAllowed(nextState))
		return -1;
	if (d->currentParams == csp)
		lWarning() << "CallSession::update() is given the current params, this is probably not what you intend to do!";
	if (csp)
		d->setParams(new CallSessionParams(*csp));
	d->op->setLocalBody(content ? *content : Content());
	LinphoneStatus result = d->startUpdate(subject);
	if (result && (d->state != initialState)) {
		/* Restore initial state */
		d->setState(initialState, "Restore initial state");
	}
	return result;
}

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

LinphoneCallDir CallSession::getDirection () const {
	L_D();
	return d->direction;
}

const Address& CallSession::getDiversionAddress () const {
	L_D();
	if (d->op && d->op->getDiversionAddress()) {
		char *addrStr = sal_address_as_string(d->op->getDiversionAddress());
		d->diversionAddress = Address(addrStr);
		bctbx_free(addrStr);
	} else {
		d->diversionAddress = Address();
	}
	return d->diversionAddress;
}

int CallSession::getDuration () const {
	L_D();
	switch (d->state) {
		case CallSession::State::End:
		case CallSession::State::Error:
		case CallSession::State::Released:
			return d->log->duration;
		default:
			return d->computeDuration();
	}
}

const LinphoneErrorInfo * CallSession::getErrorInfo () const {
	L_D();
	if (!d->nonOpError)
		linphone_error_info_from_sal_op(d->ei, d->op);
	return d->ei;
}

const Address& CallSession::getLocalAddress () const {
	L_D();
	return *L_GET_CPP_PTR_FROM_C_OBJECT((d->direction == LinphoneCallIncoming)
		? linphone_call_log_get_to(d->log) : linphone_call_log_get_from(d->log));
}

LinphoneCallLog * CallSession::getLog () const {
	L_D();
	return d->log;
}

LinphoneReason CallSession::getReason () const {
	return linphone_error_info_get_reason(getErrorInfo());
}

shared_ptr<CallSession> CallSession::getReferer () const {
	L_D();
	return d->referer;
}

string CallSession::getReferTo () const {
	L_D();
	return d->referTo;
}

const Address& CallSession::getRemoteAddress () const {
	L_D();
	return *L_GET_CPP_PTR_FROM_C_OBJECT((d->direction == LinphoneCallIncoming)
		? linphone_call_log_get_from(d->log) : linphone_call_log_get_to(d->log));
}

string CallSession::getRemoteContact () const {
	L_D();
	if (d->op) {
		/* sal_op_get_remote_contact preserves header params */
		return d->op->getRemoteContact();
	}
	return string();
}

const Address *CallSession::getRemoteContactAddress () const {
	L_D();
	if (!d->op) {
		return nullptr;
	}
	char *addrStr = sal_address_as_string(d->op->getRemoteContactAddress());
	d->remoteContactAddress = Address(addrStr);
	bctbx_free(addrStr);
	return &d->remoteContactAddress;
}

const CallSessionParams * CallSession::getRemoteParams () {
	L_D();
	if (d->op){
		const SalCustomHeader *ch = d->op->getRecvCustomHeaders();
		if (ch) {
			/* Instanciate a remote_params only if a SIP message was received before (custom headers indicates this) */
			if (!d->remoteParams)
				d->remoteParams = new CallSessionParams();
			d->remoteParams->getPrivate()->setCustomHeaders(ch);
		}

		const list<Content> additionnalContents = d->op->getAdditionalRemoteBodies();
		for (auto& content : additionnalContents)
			d->remoteParams->addCustomContent(content);

		return d->remoteParams;
	}
	return nullptr;
}

CallSession::State CallSession::getState () const {
	L_D();
	return d->state;
}

CallSession::State CallSession::getPreviousState () const {
	L_D();
	return d->prevState;
}

const Address& CallSession::getToAddress () const {
	L_D();
	return *L_GET_CPP_PTR_FROM_C_OBJECT(linphone_call_log_get_to(d->log));
}

CallSession::State CallSession::getTransferState () const {
	L_D();
	return d->transferState;
}

shared_ptr<CallSession> CallSession::getTransferTarget () const {
	L_D();
	return d->transferTarget;
}

string CallSession::getToHeader (const string &name) const {
	L_D();
	return L_C_TO_STRING(sal_custom_header_find(d->op->getRecvCustomHeaders(), name.c_str()));
}

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

string CallSession::getRemoteUserAgent () const {
	L_D();
	if (d->op)
		return d->op->getRemoteUserAgent();
	return string();
}

shared_ptr<CallSession> CallSession::getReplacedCallSession () const {
	L_D();
	SalOp *replacedOp = d->op->getReplaces();
	if (!replacedOp)
		return nullptr;
	return reinterpret_cast<CallSession *>(replacedOp->getUserPointer())->getSharedFromThis();
}

CallSessionParams * CallSession::getCurrentParams () const {
	L_D();
	d->updateCurrentParams();
	return d->currentParams;
}

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

const CallSessionParams * CallSession::getParams () const {
	L_D();
	return d->params;
}

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

bool CallSession::isEarlyState (CallSession::State state) {
	switch (state) {
		case CallSession::State::Idle:
		case CallSession::State::OutgoingInit:
		case CallSession::State::OutgoingEarlyMedia:
		case CallSession::State::OutgoingRinging:
		case CallSession::State::OutgoingProgress:
		case CallSession::State::IncomingReceived:
		case CallSession::State::IncomingEarlyMedia:
		case CallSession::State::EarlyUpdatedByRemote:
		case CallSession::State::EarlyUpdating:
			return true;
		default:
			return false;
	}
}

LINPHONE_END_NAMESPACE
