/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2008-2009  Michael Bell <michael.bell@opensync.org>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */

#include "syncml.h"

#include "syncml_internals.h"
#include "sml_error_internals.h"

/**
 * @defgroup SmlErrorPrivate SyncML Error Internals
 * @ingroup PrivateLowLevelAPI
 * @brief The private part of the error handling
 * 
 */
/*@{*/

/*! @brief Returns a string representation for a SyncML error.
 *
 * Please see the OMA documents for the according copyrights.
 * 
 * @param type  The type to set
 * @returns     The error message as const string.
 *              If NULL is returned then this is not an error.
 *              If a string with the length zero is returned
 *              then there is no error message for this error.
 *              This is a bug and you can file a ticket.
 * 
 */
const char *smlErrorTypeGetMessage(SmlErrorType type)
{
	if (SML_ERROR_UNKNOWN)
		return "An unkown error occured.";

	/* return NULL if this is not an error */
	if (type < 300)
		return NULL;

	/* Errors according to SyncML Representation Protocol 1.2.1
	 * The error messages are the original phrases like specified
	 * by the OMA in the original specifications.
	 */
	switch (type)
	{
		/* Retry error */
		case SML_ERROR_MULTIPLE_CHOICES:
			return  "Multiple choices. The requested target is one of a number of " \
				"multiple alternatives requested target. The alternative SHOULD " \
				"also be returned in the Item element type in the Status.";
		case SML_ERROR_MOVED_PERMANENTLY:
			return  "Moved permanently. The requested target has a new URI. The new " \
				"URI SHOULD also be returned in the Item element type in the " \
				"Status.";
		case SML_ERROR_FOUND_RETRY:
			return  "Found. The requested target has temporarily moved to a different " \
				"URI. The original URI SHOULD continue to be used. The URI of " \
				"the temporary location SHOULD also be returned in the Item " \
				"element type in the Status. The requestor SHOULD confirm the " \
				"identity and authority of the temporary URI to act on behalf of the " \
				"original target URI.";
		case SML_ERROR_SEE_OTHER_RETRY:
			return  "See other. The requested target can be found at another URI. The " \
				"other URI SHOULD be returned in the Item element type in the " \
				"Status.";
		case SML_ERROR_NOT_MODIFIED:
			return  "Not modified. The requested SyncML command was not executed " \
				"on the target. This is an additional response that can be added to " \
				"any of the other Redirection response codes.";
		case SML_ERROR_USE_PROXY:
			return  "Use proxy. The requested target MUST be accessed through the " \
				"specified proxy URI. The proxy URI SHOULD also be returned in " \
				"the Item element type in the Status.";

		/* Errors */
		case SML_ERROR_BAD_REQUEST:
			return  "Bad request. The requested command could not be performed " \
				"because of malformed syntax in the command. The malformed " \
				"command MAY also be returned in the Item element type in the " \
				"Status.";
		case SML_ERROR_AUTH_REJECTED:
			return  "Invalid credentials. The requested command failed because the " \
				"requestor MUST provide proper authentication. If the property type " \
				"of authentication was presented in the original request, then the " \
				"response code indicates that the requested command has been " \
				"refused for those credentials.";
		case SML_ERROR_PAYMENT_NEEDED:
			return  "Paymentneeded. The requested command failed because proper " \
				"payment isneeded. This version of SyncML does not standardize " \
				"the payment mechanism.";
		case SML_ERROR_FORBIDDEN:
			return  "Forbidden. The requested command failed, but the recipient " \
				"understood the requested command. Authentication will not help " \
				"and the request SHOULD NOT be repeated. If the recipient wishes " \
				"to make public why the request was denied, then a description " \
				"MAY be specified in the Item element type in the Status. If the " \
				"recipient does not wish to make public why the request was denied " \
				"then the response code 404 MAY be used instead.";
		case SML_ERROR_NOT_FOUND:
			return  "Not found. The requested target was not found. No indication is " \
				"given as to whether this is a temporary or permanent condition. The " \
				"response code 410 SHOULD be used when the condition is " \
				"permanent and the recipient wishes to make this fact public. This " \
				"response code is also used when the recipient does not want to " \
				"make public the reason for why a requested command is not " \
				"allowed or when no other response code is appropriate.";
		case SML_ERROR_UNSUPPORTED_FEATURE:
			return  "Optional feature not supported. The requested command failed " \
				"because an OPTIONAL feature in the request was not supported. " \
				"The unsupported feature SHOULD be specified by the Item " \
				"element type in the Status.";
		case SML_ERROR_RETRY_LATER:
			return  "Retry later. The request failed at this time and the originator " \
				"SHOULD retry the request later. The recipient SHOULD specify a " \
				"RespURI with the response URI and the date/time that the " \
				"command SHOULD be repeated.";
		case SML_ERROR_ALREADY_EXISTS:
			return  "Already exists. The requested Put or Add command failed " \
				"because the target already exists.";
		case SML_ERROR_SIZE_MISMATCH:
			return  "Size mismatch. The chunked object was received, but the size of " \
				"the received object did not match the size declared within the first " \
				"chunk.";
	
		/* Standard errors */
		case SML_ERROR_GENERIC:
			return  "Command failed. The recipient encountered an unexpected " \
				"condition which prevented it from fulfilling the request.";
		case SML_ERROR_NOT_IMPLEMENTED:
			return  "Command not implemented. The recipient does not support the " \
				"command REQUIRED to fulfill the request. This is the appropriate " \
				"response when the recipient does not recognize the requested " \
				"command and is not capable of supporting it for any resource.";
		case SML_ERROR_SERVICE_UNAVAILABLE:
			return  "Service unavailable. The recipient is currently unable to handle the " \
				"request due to a temporary overloading or maintenance of the " \
				"recipient. The implication is that this is a temporary condition; " \
				"which will be alleviated after some delay. The recipient SHOULD " \
				"specify the URI and date/time after which the originator SHOULD " \
				"retry in the RespURI in the response.";
		case SML_ERROR_REQUIRE_REFRESH:
			return  "RefreshREQUIRED. An error occurred that necessitates a refresh " \
				"of the current synchronization state of the client with the server. " \
				"Client is requested to initiate the session type specified in the " \
				"server's ALERT (which is included in the same package as the " \
				"Status 508). The only valid values for this ALERT are either a slow " \
				"sync (201) or a refresh with the server.";
		case SML_ERROR_SERVER_FAILURE:
			return	"Server failure. A severe error occurred in the server while " \
				"processing the request. The originator SHOULD NOT retry the " \
				"request.";

		/* Internal errors */
		case SML_ERROR_INTERNAL_IO_ERROR:
			return  "An internal I/O error occured.";
		case SML_ERROR_INTERNAL_TIMEOUT:
			return  "An internal timeout occured.";
		case SML_ERROR_INTERNAL_FILE_NOT_FOUND:
			return  "An internal error occured because of a missing file.";
		case SML_ERROR_INTERNAL_MISCONFIGURATION:
			return  "An internal misconfiguration was detected.";
		case SML_ERROR_INTERNAL_NO_MEMORY:
			return  "An internal I/O error occured.";

		default:
			return "";
	}
}

/*! @brief Sets a error from a va_list
 * 
 * @param error A pointer to a error struct
 * @param type The type to set
 * @param format The message
 * @param args The arguments to the message
 * 
 */
void smlErrorSetVargs(SmlError **error, SmlErrorType type, const char *format, va_list args)
{
	return_if_fail(error);
	return_if_fail(smlErrorIsSet(error) == FALSE);
	return_if_fail(format);

	*error = (SmlError *) malloc(sizeof(SmlError));
	return_if_fail(*error);
	(*error)->message = g_strdup_vprintf(format, args);
	(*error)->type = type;
	const char *msg = smlErrorTypeGetMessage(type);
	if (!msg) {
		(*error)->printMessage = g_strdup((*error)->message);
	} else if (strlen(msg) == 0) {
		(*error)->printMessage = g_strdup_printf(
						"Error %d: %s",
						(*error)->type,
						(*error)->message);
	} else {
		(*error)->printMessage = g_strdup_printf(
						"%s %s",
						msg,
						(*error)->message);
	}
	(*error)->refCount = 1;
	
	return;
}

/*@}*/

/**
 * @defgroup SmlError SyncML Errors
 * @ingroup PublicLowLevelAPI
 * @brief Libsyncml's error reporting facilities
 * 
 */
/*@{*/

SmlError **smlErrorRef(SmlError **error)
{
	if (!smlErrorIsSet(error))
		return error;
	
	g_atomic_int_inc(&(*error)->refCount);

	return error;
}

void smlErrorDeref(SmlError **error)
{
	if (!smlErrorIsSet(error))
		return;
		
	if (!g_atomic_int_dec_and_test(&((*error)->refCount))) {
		*error = NULL;
		return;
	}
	
	if ((*error)->message)
		smlSafeCFree (&((*error)->message));
		
	if ((*error)->printMessage)
		smlSafeCFree (&((*error)->printMessage));

	smlSafeFree((gpointer *)error);
}

/*! @brief Checks if the error is set
 * 
 * @param error A pointer to a error struct to check
 * @returns TRUE if the error is set, FALSE otherwise
 * 
 */
SmlBool smlErrorIsSet(SmlError **error)
{
	if (!error)
		return FALSE;
		
	if (*error == NULL)
		return FALSE;
		
	return TRUE;
}

/*! @brief Returns the type of the error
 * 
 * @param error The error
 * @returns The type of the error
 * 
 */
SmlErrorType smlErrorGetType(SmlError **error)
{
	if (!smlErrorIsSet(error))
		return SML_NO_ERROR;
	
	return (*error)->type;
}

/*! @brief Returns the message of the error
 * 
 * @param error The error to print
 * @returns The message of the error or NULL if no error
 * 
 */
const char *smlErrorPrint(SmlError **error)
{
	if (!smlErrorIsSet(error))
		return NULL;
		
	return (*error)->printMessage;
}

/*! @brief Updates the error message
 * 
 * You can use this function to update the error message on
 * a error. You can use the old error->message as a parameter
 * for this function.
 * 
 * @param error A pointer to a error struct to update
 * @param format The new message
 * 
 */
void smlErrorUpdate(SmlError **error, const char *format, ...)
{
	return_if_fail(smlErrorIsSet(error));

	char *old = (*error)->message;
	va_list args;
	va_start(args, format);
	(*error)->message = g_strdup_vprintf(format, args);
	va_end (args);

	smlSafeCFree(&old);
}

/*! @brief Duplicates the error into the target
 * 
 * 
 * @param target The target error to update
 * @param source The source error which to duplicate
 * 
 */
void smlErrorDuplicate(SmlError **target, SmlError **source)
{
	return_if_fail(target != NULL);
	return_if_fail(smlErrorIsSet(source));
	return_if_fail(!smlErrorIsSet(target));
	
	smlErrorSet(target, (*source)->type, (*source)->message);
}

/*! @brief Sets the error
 * 
 * You can use this function to set the error to the given type and message
 * 
 * @param error A pointer to a error struct to set
 * @param type The Error type to set
 * @param format The message
 * 
 */
void smlErrorSet(SmlError **error, SmlErrorType type, const char *format, ...)
{
	va_list args;
	va_start(args, format);
	smlErrorSetVargs(error, type, format, args);
	va_end (args);
}

/*! @brief Sets the type of an error
 * 
 * @param error A pointer to a error struct to set
 * @param type The Error type to set
 * 
 */
void smlErrorSetType(SmlError **error, SmlErrorType type)
{
	return_if_fail(error != NULL);
	
	(*error)->type = type;
}

/*! @brief Gets the error class
 * 
 * @param error A pointer to a error struct
 * @returns The error class
 * 
 */
SmlErrorClass smlErrorGetClass(SmlError **error)
{
	if (!smlErrorIsSet(error))
		return SML_ERRORCLASS_SUCCESS;

	if ((*error)->type == SML_NO_ERROR)
		return SML_ERRORCLASS_SUCCESS;
		
	return (int)((*error)->type / 100);
}

/*@}*/
