/*
    libparted - a library for manipulating disk partitions
    Copyright (C) 1999-2000, 2007-2014, 2019-2021 Free Software Foundation,
    Inc.

    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/>.
*/

/** \file exception.c */

/**
 * \addtogroup PedException
 *
 * \brief Exception handling.
 *
 * There are a few types of exceptions: PED_EXCEPTION_INFORMATION,
 * PED_EXCEPTION_WARNING, PED_EXCEPTION_ERROR, PED_EXCEPTION_FATAL,
 * PED_EXCEPTION_BUG.
 *
 * They are "thrown" when one of the above events occur while executing
 * a libparted function. For example, if ped_device_open() fails
 * because the device doesn't exist, an exception will be thrown.
 * Exceptions contain text describing what the event was. It will give
 * at least one option for resolving the exception: PED_EXCEPTION_FIX,
 * PED_EXCEPTION_YES, PED_EXCEPTION_NO, PED_EXCEPTION_OK, PED_EXCEPTION_RETRY,
 * PED_EXCEPTION_IGNORE, PED_EXCEPTION_CANCEL. After an exception is thrown,
 * there are two things that can happen:
 *
 * -# an exception handler is called, which selects how the exception should be
 *    resolved (usually by asking the user). Also note: an exception handler may
 *    choose to return PED_EXCEPTION_UNHANDLED. In this case, a default action
 *    will be taken by libparted (respectively the code that threw the
 *    exception). In general, a default action will be "safe".
 * -# the exception is not handled, because the caller of the function wants to
 *    handle everything itself. In this case, PED_EXCEPTION_UNHANDLED is
 *    returned.
 *
 * @{
 */

#include <config.h>

#include <parted/parted.h>
#include <parted/debug.h>
#include <parted/exception.h>

#define N_(String) String
#if ENABLE_NLS
#  include <libintl.h>
#  define _(String) dgettext (PACKAGE, String)
#else
#  define _(String) (String)
#endif /* ENABLE_NLS */

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

int				ped_exception = 0;

static PedExceptionOption default_handler (PedException* ex);

static PedExceptionHandler*	ex_handler = default_handler;
static PedException*		ex = NULL;
static int			ex_fetch_count = 0;

static const char *const type_strings [] = {
	N_("Information"),
	N_("Warning"),
	N_("Error"),
	N_("Fatal"),
	N_("Bug"),
	N_("No Implementation")
};

static const char *const option_strings [] = {
	N_("Fix"),
	N_("Yes"),
	N_("No"),
	N_("OK"),
	N_("Retry"),
	N_("Ignore"),
	N_("Cancel")
};

/**
 *  Return a string describing an exception type.
 */
char*
ped_exception_get_type_string (PedExceptionType ex_type)
{
	return (char *) type_strings [ex_type - 1];
}

/* FIXME: move this out to the prospective math.c */
/* FIXME: this can probably be done more efficiently */
static int _GL_ATTRIBUTE_PURE
ped_log2 (int n)
{
	int x;

        PED_ASSERT (n > 0);

	for (x=0; 1 << x <= n; x++);

	return x - 1;
}

/**
 * Return a string describing an exception option.
 */
char*
ped_exception_get_option_string (PedExceptionOption ex_opt)
{
	return (char *) option_strings [ped_log2 (ex_opt)];
}

static PedExceptionOption
default_handler (PedException* e)
{
	if (e->type == PED_EXCEPTION_BUG)
		fprintf (stderr,
			_("A bug has been detected in GNU Parted.  "
			"Refer to the web site of parted "
			"http://www.gnu.org/software/parted/parted.html "
			"for more information of what could be useful "
			"for bug submitting!  "
			"Please email a bug report to "
			"%s containing at least the "
			"version (%s) and the following message:  "),
			 PACKAGE_BUGREPORT, VERSION);
	else
		fprintf (stderr, "%s: ",
			 ped_exception_get_type_string (e->type));
	fprintf (stderr, "%s\n", e->message);

	switch (e->options) {
		case PED_EXCEPTION_OK:
		case PED_EXCEPTION_CANCEL:
		case PED_EXCEPTION_IGNORE:
			return e->options;

		default:
			return PED_EXCEPTION_UNHANDLED;
	}
}

/**
 * Set the exception handler.
 *
 * The exception handler should return ONE of the options set in ex->options,
 * indicating the way the event should be resolved.
 */
void
ped_exception_set_handler (PedExceptionHandler* handler)
{
	if (handler)
		ex_handler = handler;
	else
		ex_handler = default_handler;
}

/**
 * Get the current exception handler.
 */
PedExceptionHandler *
ped_exception_get_handler (void)
{
	if (ex_handler)
		return ex_handler;
	return default_handler;
}

/**
 * Assert that the current exception has been resolved.
 */
void
ped_exception_catch ()
{
        if (ped_exception) {
                ped_exception = 0;
                free (ex->message);
                free (ex);
                ex = NULL;
        }
}

static PedExceptionOption
do_throw ()
{
	PedExceptionOption	ex_opt;

	ped_exception = 1;

	if (ex_fetch_count) {
		return PED_EXCEPTION_UNHANDLED;
	} else {
		ex_opt = ex_handler (ex);
		ped_exception_catch ();
		return ex_opt;
	}
}

/**
 * Throw an exception.
 *
 * You can also use this in a program using libparted.
 * "message" is a printf-like format string, so you can do
 *
 * \code
 * ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_CANCEL,
 *      "Can't open %s", file_name);
 * \endcode
 *
 * Returns the option selected to resolve the exception. If the exception was
 * unhandled, PED_EXCEPTION_UNHANDLED is returned.
 */
PedExceptionOption
ped_exception_throw (PedExceptionType ex_type,
		     PedExceptionOption ex_opts, const char* message, ...)
{
	va_list		arg_list;
	int result;
	static int size = 1000;

	if (ex)
		ped_exception_catch ();

	ex = (PedException*) malloc (sizeof (PedException));
	if (!ex)
		goto no_memory;

	ex->type = ex_type;
	ex->options = ex_opts;

	while (message) {
			ex->message = (char*) malloc (size * sizeof (char));
			if (!ex->message)
					goto no_memory;

			va_start (arg_list, message);
			result = vsnprintf (ex->message, size, message, arg_list);
			va_end (arg_list);

			if (result > -1 && result < size)
					break;

			size += 10;
			free (ex->message);
	}

	return do_throw ();

no_memory:
	fputs ("Out of memory in exception handler!\n", stderr);

	va_start (arg_list, message);
	vfprintf (stderr, message, arg_list);
	va_end (arg_list);

	return PED_EXCEPTION_UNHANDLED;
}

/**
 * Rethrow an unhandled exception.
 * This means repeating the last ped_exception_throw() statement.
 */
PedExceptionOption
ped_exception_rethrow ()
{
	return do_throw ();
}

/**
 * Indicates that exceptions should not go to the exception handler, but
 * passed up to the calling function(s).  All calls to
 * ped_exception_throw() will return PED_EXCEPTION_UNHANDLED.
 */
void
ped_exception_fetch_all ()
{
	ex_fetch_count++;
}

/**
 * Indicates that the calling function does not want to accept any
 * responsibility for exceptions any more.
 *
 * \note a caller of that function may still want responsibility, so
 *      ped_exception_throw() may not invoke the exception handler.
 *
 * \warning every call to this function must have a preceding
 *      ped_exception_fetch_all().
 */
void
ped_exception_leave_all ()
{
	PED_ASSERT (ex_fetch_count > 0);
	ex_fetch_count--;
}

/** @} */
