/*
 * 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 "linphone/core.h"

#include "c-wrapper/c-wrapper.h"

// TODO: From coreapi. Remove me later.
#include "private.h"

BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneErrorInfo);

static void linphone_error_info_reset(LinphoneErrorInfo *ei);

static void error_info_destroy(LinphoneErrorInfo *ei) {
	linphone_error_info_reset(ei);
}

static void error_info_clone(LinphoneErrorInfo *ei, const LinphoneErrorInfo *other) {
	linphone_error_info_set_reason(ei, linphone_error_info_get_reason(other));
	ei->protocol = bctbx_strdup(other->protocol);
	ei->phrase = bctbx_strdup(other->phrase);
	ei->warnings = bctbx_strdup(other->warnings);
	ei->full_string = bctbx_strdup(other->full_string);
	ei->protocol_code = other->protocol_code;
	ei->retry_after = other->retry_after;
}

BELLE_SIP_INSTANCIATE_VPTR(LinphoneErrorInfo,
                           belle_sip_object_t,
                           error_info_destroy, // destroy
                           error_info_clone,   // clone
                           NULL,               // Marshall
                           FALSE);

LinphoneErrorInfo *linphone_error_info_new(void) {
	LinphoneErrorInfo *ei = belle_sip_object_new(LinphoneErrorInfo);
	return ei;
}

LinphoneErrorInfo *linphone_error_info_ref(LinphoneErrorInfo *ei) {
	return (LinphoneErrorInfo *)belle_sip_object_ref(ei);
}

void linphone_error_info_unref(LinphoneErrorInfo *ei) {
	belle_sip_object_unref(ei);
}

const char *linphone_reason_to_string(LinphoneReason err) {
	switch (err) {
		case LinphoneReasonNone:
			return "No error";
		case LinphoneReasonNoResponse:
			return "No response";
		case LinphoneReasonForbidden:
			return "Bad credentials";
		case LinphoneReasonDeclined:
			return "Call declined";
		case LinphoneReasonNotFound:
			return "User not found";
		case LinphoneReasonNotAnswered:
			return "Not answered";
		case LinphoneReasonBusy:
			return "Busy";
		case LinphoneReasonMedia:
			return "Incompatible media capabilities";
		case LinphoneReasonIOError:
			return "IO error";
		case LinphoneReasonDoNotDisturb:
			return "Do not disturb";
		case LinphoneReasonUnauthorized:
			return "Unauthorized";
		case LinphoneReasonNotAcceptable:
			return "Not acceptable here";
		case LinphoneReasonNoMatch:
			return "No match";
		case LinphoneReasonMovedPermanently:
			return "Moved permanently";
		case LinphoneReasonGone:
			return "Gone";
		case LinphoneReasonBadEvent:
			return "Bad event";
		case LinphoneReasonTemporarilyUnavailable:
			return "Temporarily unavailable";
		case LinphoneReasonAddressIncomplete:
			return "Address incomplete";
		case LinphoneReasonNotImplemented:
			return "Not implemented";
		case LinphoneReasonBadGateway:
			return "Bad gateway";
		case LinphoneReasonSessionIntervalTooSmall:
			return "Session Interval Too Small";
		case LinphoneReasonServerTimeout:
			return "Server timeout";
		case LinphoneReasonTransferred:
			return "Call transferred";
		case LinphoneReasonConditionalRequestFailed:
			return "Conditional request failed";
		case LinphoneReasonUnknown:
			return "Unknown error";
	}
	return "unknown error";
}

typedef struct _error_code_reason_map {
	int error_code;
	LinphoneReason reason;
} error_code_reason_map_t;

static const error_code_reason_map_t error_code_reason_map[] = {{200, LinphoneReasonNone},
                                                                {301, LinphoneReasonMovedPermanently},
                                                                {400, LinphoneReasonUnknown},
                                                                {401, LinphoneReasonUnauthorized},
                                                                {403, LinphoneReasonForbidden},
                                                                {404, LinphoneReasonNotFound},
                                                                {410, LinphoneReasonGone},
                                                                {412, LinphoneReasonConditionalRequestFailed},
                                                                {415, LinphoneReasonUnsupportedContent},
                                                                {480, LinphoneReasonTemporarilyUnavailable},
                                                                {481, LinphoneReasonNoMatch},
                                                                {484, LinphoneReasonAddressIncomplete},
                                                                {486, LinphoneReasonBusy},
                                                                {488, LinphoneReasonNotAcceptable},
                                                                {489, LinphoneReasonBadEvent},
                                                                {501, LinphoneReasonNotImplemented},
                                                                {502, LinphoneReasonBadGateway},
                                                                {503, LinphoneReasonIOError},
                                                                {504, LinphoneReasonServerTimeout},
                                                                {600, LinphoneReasonDoNotDisturb},
                                                                {603, LinphoneReasonDeclined}};

static const error_code_reason_map_t retry_after_code_reason_map[] = {
    {404, LinphoneReasonNotFound}, {480, LinphoneReasonTemporarilyUnavailable}, {486, LinphoneReasonBusy},
    {503, LinphoneReasonIOError},  {600, LinphoneReasonDoNotDisturb},           {603, LinphoneReasonDeclined}};

bool_t linphone_error_code_is_retry_after(int err) {
	size_t i;
	for (i = 0; i < (sizeof(retry_after_code_reason_map) / sizeof(retry_after_code_reason_map[0])); i++) {
		if (retry_after_code_reason_map[i].error_code == err) return true;
	}
	return false;
}

LinphoneReason linphone_error_code_to_reason(int err) {
	size_t i;
	for (i = 0; i < (sizeof(error_code_reason_map) / sizeof(error_code_reason_map[0])); i++) {
		if (error_code_reason_map[i].error_code == err) return error_code_reason_map[i].reason;
	}
	return LinphoneReasonUnknown;
}

int linphone_reason_to_error_code(LinphoneReason reason) {
	size_t i;
	for (i = 0; i < (sizeof(error_code_reason_map) / sizeof(error_code_reason_map[0])); i++) {
		if (error_code_reason_map[i].reason == reason) return error_code_reason_map[i].error_code;
	}
	return 400;
}

static void linphone_error_info_reset(LinphoneErrorInfo *ei) {
	ei->reason = LinphoneReasonNone;
	STRING_RESET(ei->protocol);
	STRING_RESET(ei->phrase);
	STRING_RESET(ei->full_string);
	STRING_RESET(ei->warnings);
	ei->protocol_code = 0;
	ei->retry_after = 0;
	if (ei->sub_ei) {
		linphone_error_info_unref(ei->sub_ei);
		ei->sub_ei = NULL;
	}
}

void linphone_error_info_from_sal(LinphoneErrorInfo *ei, const SalErrorInfo *sei) {
	ei->reason = linphone_reason_from_sal(sei->reason);
	ei->phrase = bctbx_strdup(sei->status_string);
	ei->full_string = bctbx_strdup(sei->full_string);
	ei->warnings = bctbx_strdup(sei->warnings);
	ei->protocol_code = sei->protocol_code;
	ei->retry_after = sei->retry_after;
	ei->protocol = bctbx_strdup(sei->protocol);
}

/* If a reason header is provided (in reason_ei), then create a sub LinphoneErrorInfo attached to the first one, unless
 the reason header is in the request, in which case no primary error is given.*/
void linphone_error_info_from_sal_reason_ei(LinphoneErrorInfo *ei, const SalErrorInfo *reason_ei) {
	if (ei->reason == LinphoneReasonNone) {
		/*no primary error given*/
		linphone_error_info_reset(ei);
		linphone_error_info_from_sal(ei, reason_ei);
		return;
	}

	if (ei->sub_ei) {
		if (reason_ei->reason == SalReasonNone) {
			linphone_error_info_unref(ei->sub_ei);
			ei->sub_ei = NULL;
		}
	} else {
		if (reason_ei->reason != SalReasonNone) {
			ei->sub_ei = linphone_error_info_new();
		}
	}
	if (reason_ei->reason != SalReasonNone) {
		linphone_error_info_from_sal(ei->sub_ei, reason_ei);
	}
}

void linphone_error_info_from_sal_op(LinphoneErrorInfo *ei, const LinphonePrivate::SalOp *op) {
	if (op == NULL) {
		/*leave previous values in LinphoneErrorInfo, the op may have been released already.*/
		return;
	} else {
		const SalErrorInfo *sei;
		linphone_error_info_reset(ei);
		sei = op->getErrorInfo();
		linphone_error_info_from_sal(ei, sei);
		sei = op->getReasonErrorInfo();
		linphone_error_info_from_sal_reason_ei(ei, sei);
	}
}

void linphone_error_info_fields_to_sal(const LinphoneErrorInfo *ei, SalErrorInfo *sei) {
	sei->reason = linphone_reason_to_sal(linphone_error_info_get_reason(ei));
	sei->status_string = bctbx_strdup(ei->phrase);
	sei->full_string = bctbx_strdup(ei->full_string);
	sei->warnings = bctbx_strdup(ei->warnings);
	sei->protocol_code = ei->protocol_code;
	sei->retry_after = ei->retry_after;
	sei->protocol = bctbx_strdup(ei->protocol);
}

void linphone_error_info_to_sal(const LinphoneErrorInfo *ei, SalErrorInfo *sei) {

	linphone_error_info_fields_to_sal(ei, sei);
	if (ei->sub_ei != NULL) {

		linphone_error_info_to_sal(ei->sub_ei, sei->sub_sei);
	}
}

void linphone_error_info_set(LinphoneErrorInfo *ei,
                             const char *protocol,
                             LinphoneReason reason,
                             int code,
                             const char *status_string,
                             const char *warning) {
	linphone_error_info_reset(ei);
	ei->reason = reason;
	ei->protocol_code = code;
	ei->protocol = bctbx_strdup(protocol ? protocol : "SIP");
	ei->phrase = bctbx_strdup(status_string);
	ei->warnings = bctbx_strdup(warning);
}

int linphone_error_info_get_retry_after(const LinphoneErrorInfo *ei) {
	return ei->retry_after;
}

LinphoneReason linphone_error_info_get_reason(const LinphoneErrorInfo *ei) {
	return ei->reason;
}

const char *linphone_error_info_get_protocol(const LinphoneErrorInfo *ei) {
	return ei->protocol;
}

const char *linphone_error_info_get_phrase(const LinphoneErrorInfo *ei) {
	return ei->phrase;
}

/*deprecated, kept for binary compatibility*/
const char *linphone_error_info_get_details(const LinphoneErrorInfo *ei) {
	return linphone_error_info_get_warnings(ei);
}

const char *linphone_error_info_get_warnings(const LinphoneErrorInfo *ei) {
	return ei->warnings;
}

int linphone_error_info_get_protocol_code(const LinphoneErrorInfo *ei) {
	return ei->protocol_code;
}

LinphoneErrorInfo *linphone_error_info_get_sub_error_info(const LinphoneErrorInfo *ei) {
	return ei->sub_ei;
}

void linphone_error_info_set_retry_after(LinphoneErrorInfo *ei, int retry_after) {
	ei->retry_after = retry_after;
}

void linphone_error_info_set_reason(LinphoneErrorInfo *ei, LinphoneReason reason) {
	ei->reason = reason;
}

void linphone_error_info_set_protocol(LinphoneErrorInfo *ei, const char *proto) {
	STRING_SET(ei->protocol, proto);
}

void linphone_error_info_set_protocol_code(LinphoneErrorInfo *ei, int code) {
	ei->protocol_code = code;
}

void linphone_error_info_set_phrase(LinphoneErrorInfo *ei, const char *phrase) {
	STRING_SET(ei->phrase, phrase);
}

void linphone_error_info_set_warnings(LinphoneErrorInfo *ei, const char *warnings) {
	STRING_SET(ei->warnings, warnings);
}

void linphone_error_info_set_sub_error_info(LinphoneErrorInfo *ei, LinphoneErrorInfo *appended_ei) {
	if (ei->sub_ei) {
		linphone_error_info_unref(ei->sub_ei);
		ei->sub_ei = NULL;
	}
	if (appended_ei != NULL) {
		ei->sub_ei = linphone_error_info_ref(appended_ei);
	}
}
