/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of Liblinphone
 * (see https://gitlab.linphone.org/BC/public/liblinphone).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "sal/register-op.h"
#include "bctoolbox/defs.h"
#include "bellesip_sal/sal_impl.h"

using namespace std;

LINPHONE_BEGIN_NAMESPACE

int SalRegisterOp::sendRegister(const SalAddress *proxy,
                                const SalAddress *from,
                                int expires,
                                const std::list<SalAddress *> &customContacts) {
	if (mRefresher) {
		belle_sip_refresher_stop(mRefresher);
		belle_sip_object_unref(mRefresher);
		mRefresher = nullptr;
	}

	setFromAddress(from);
	setToAddress(from);
	setRouteAddress(proxy);
	auto request = buildRequest("REGISTER");
	if (request == nullptr) return -1;
	auto requestUri = belle_sip_request_get_uri(request);
	belle_sip_uri_set_user(requestUri, nullptr); // Remove userinfo if there is any
	if (mRoot->mUseDates) {
		time_t curtime = time(nullptr);
		belle_sip_message_add_header(BELLE_SIP_MESSAGE(request),
		                             BELLE_SIP_HEADER(belle_sip_header_date_create_from_time(&curtime)));
	}
	auto acceptHeader =
	    belle_sip_header_create("Accept", "application/sdp, text/plain, application/vnd.gsma.rcs-ft-http+xml");
	belle_sip_message_add_header(BELLE_SIP_MESSAGE(request), acceptHeader);
	belle_sip_message_set_header(BELLE_SIP_MESSAGE(request), BELLE_SIP_HEADER(createContact(true)));

	for (const SalAddress *customContact : customContacts) {
		auto contactHeader = belle_sip_header_contact_create(BELLE_SIP_HEADER_ADDRESS(customContact));
		if (contactHeader) {
			belle_sip_message_add_header(BELLE_SIP_MESSAGE(request), BELLE_SIP_HEADER(contactHeader));
			char *tmp = belle_sip_object_to_string(contactHeader);
			lInfo() << "Added custom contact [" << tmp << "] for op [" << this << "]";
			ms_free(tmp);
		} else {
			lError() << "Cannot add custom contact header to op [" << this << "]";
		}
	}

	return sendRequestAndCreateRefresher(request, expires, registerRefresherListener);
}

void SalRegisterOp::registerRefresherListener(belle_sip_refresher_t *refresher,
                                              void *userCtx,
                                              unsigned int statusCode,
                                              const char *reasonPhrase,
                                              BCTBX_UNUSED(int willRetry)) {
	auto op = static_cast<SalRegisterOp *>(userCtx);
	auto response =
	    belle_sip_transaction_get_response(BELLE_SIP_TRANSACTION(belle_sip_refresher_get_transaction(refresher)));

	lInfo() << "Register refresher [" << statusCode << "] reason [" << reasonPhrase << "] for proxy [" << op->getProxy()
	        << "]";

	if (belle_sip_refresher_get_auth_events(refresher)) {
		if (op->mAuthInfo) sal_auth_info_delete(op->mAuthInfo);
		// Only take first one for now
		op->mAuthInfo = sal_auth_info_create(
		    reinterpret_cast<belle_sip_auth_event_t *>(belle_sip_refresher_get_auth_events(refresher)->data));
	}
	sal_error_info_set(&op->mErrorInfo, SalReasonUnknown, "SIP", (int)statusCode, reasonPhrase, nullptr);

	if (statusCode >= 200) op->assignRecvHeaders(BELLE_SIP_MESSAGE(response));

	if (statusCode == 200) {
		// Check service route rfc3608
		auto contactHeader = belle_sip_refresher_get_contact(refresher);
		auto serviceRouteHeader = belle_sip_message_get_header_by_type(response, belle_sip_header_service_route_t);
		belle_sip_header_address_t *serviceRouteAddressHeader = nullptr;
		if (serviceRouteHeader) {
			serviceRouteAddressHeader = belle_sip_header_address_create(
			    nullptr, belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(serviceRouteHeader)));
		}
		op->setServiceRoute(reinterpret_cast<const SalAddress *>(serviceRouteAddressHeader));
		if (serviceRouteAddressHeader) belle_sip_object_unref(serviceRouteAddressHeader);

		op->mRoot->removePendingAuth(op); // Just in case

		if (contactHeader) {
			auto p = BELLE_SIP_PARAMETERS(contactHeader);
			const char *gruu = belle_sip_parameters_get_parameter(p, "pub-gruu");

			if (gruu) {
				char *unquotedGruu = belle_sip_unquote_strdup(gruu);
				auto parsedGruu = reinterpret_cast<SalAddress *>(belle_sip_header_address_parse(unquotedGruu));

				if (parsedGruu != nullptr) {
					op->setContactAddress(parsedGruu);
				} else {
					// Gruu is not valid, act as if there was none
					op->setContactAddress(reinterpret_cast<SalAddress *>(BELLE_SIP_HEADER_ADDRESS(contactHeader)));
				}

				bctbx_free(unquotedGruu);
				belle_sip_parameters_remove_parameter(p, "pub-gruu");
			} else {
				// Update contact with real value
				op->setContactAddress(reinterpret_cast<SalAddress *>(BELLE_SIP_HEADER_ADDRESS(contactHeader)));
			}
		}
		op->mRoot->mCallbacks.register_success(op, belle_sip_refresher_get_expires(op->mRefresher) > 0);
	} else if (statusCode >= 400) {
		// From rfc3608, 6.1.:
		//     If the UA refreshes the registration, the stored value of the Service-
		//     Route is updated according to the Service-Route header field of the
		//     latest 200 class response.  If there is no Service-Route header field
		//     in the response, the UA clears any service route for that address-
		//     of-record previously stored by the UA.  If the re-registration
		//     request is refused or if an existing registration expires and the UA
		//     chooses not to re-register, the UA SHOULD discard any stored service
		//     route for that address-of-record.
		op->setServiceRoute(nullptr);
		op->ref(); // Take a ref while invoking the callback to make sure the operations done after are valid
		op->mRoot->mCallbacks.register_failure(op);
		if ((op->mState != State::Terminated) && op->mAuthInfo) {
			switch (statusCode) {
				case 401:
					BCTBX_NO_BREAK;
				case 407:
					// Add pending auth for both 401 and 407, as we got a non-working authentication.
					op->mRoot->addPendingAuth(op);
					BCTBX_NO_BREAK;
					// In any case notify the failure.
				case 403:
					op->mRoot->mCallbacks.auth_failure(op, op->mAuthInfo);
					break;
			}
		}
		op->unref();
	}
}

LINPHONE_END_NAMESPACE
