/*
 * Copyright (C) 2023 Hasl Developers
 *
 * 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, see <https://www.gnu.org/licenses/>.
 */

#ifndef HASL_CONTEXT_H
#define HASL_CONTEXT_H

#include <glib.h>
#include <glib-object.h>

#include <hasl/haslversion.h>

G_BEGIN_DECLS

#define HASL_CONTEXT_ERROR hasl_context_error_quark()

/**
 * hasl_context_error_quark:
 *
 * The error domain to identify errors in the context.
 *
 * Returns: The error domain.
 *
 * Since: 0.5
 */
HASL_AVAILABLE_IN_0_5
GQuark hasl_context_error_quark(void);

/**
 * HaslContextError:
 * @HASL_CONTEXT_ERROR_NO_MORE_MECHANISMS: The context has no more mechanisms
 *  to try.
 *
 * Error codes returned from contexts.
 *
 * Since: 0.5
 */
typedef enum {
	HASL_CONTEXT_ERROR_NO_MORE_MECHANISMS HASL_AVAILABLE_ENUMERATOR_IN_0_5,
} HaslContextError;

#define HASL_CONTEXT_DOMAIN \
	HASL_CONTEXT_ERROR \
	HASL_DEPRECATED_MACRO_IN_0_5_FOR(HASL_CONTEXT_ERROR)

/**
 * HaslContext:
 *
 * The context holds all of the data that the client knows about for
 * authentication. The [class@Mechanism]s use this data to authenticate.
 *
 * A client can limit which mechanisms the context will use with
 * [method@Context.set_allowed_mechanisms].
 *
 * The context also keeps track of which mechanisms have been tried and will
 * mark one as failed when [method@Context.next] is called. The client then
 * can call [method@Context.get_current_mechanism] and repeat until
 * successfully authenticated or all mechanisms have been exhausted.
 *
 * A simple example is found below.
 *
 * ```c
 * // After you connect to the server, you can create your context and start
 * // the authentication process.
 * HaslContext *context = NULL;
 * const char *mechanism = NULL;
 *
 * context = hasl_context_new();
 * hasl_context_set_username(context, "alice");
 * hasl_context_set_password(context, "hunter2");
 *
 * // If the user specified any mechanisms you can set them like so.
 * hasl_context_set_allowed_mechanisms(context, "PLAIN,EXTERNAL");
 *
 * // If the server advertised any mechanisms you can set them like so.
 * hasl_context_set_server_mechanisms(context, server_mechanisms);
 *
 * // Next will use allowed mechanisms if set, otherwise it will fallback to
 * // server mechanisms. If neither are set, the result of
 * // hasl_context_get_supported_mechanisms will be used.
 * mechanism = hasl_context_next(context);
 *
 * // Attempt your authentication using the mechanism name stored in
 * // mechanism.
 * ```
 *
 * ```c
 * // In your receive handler you can now pass the data into hasl_context_step.
 * HaslMechanismResult res;
 * GError *error = NULL;
 * guint8 *client_out = NULL;
 * gsize client_out_length = 0;
 *
 * // Note that if client_out is not NULL it is your responsibility to free it.
 * res = hasl_context_step(context, server_input, server_input_length,
 *                         &client_out, &client_out_length, &error);
 *
 * if(res == HASL_MECHANISM_RESULT_ERROR) {
 *     char *mechanism = NULL;
 *
 *     // Possibly handle error.
 *
 *     // Tell the context to try the next mechanism.
 *     mechanism = hasl_context_next(context);
 *
 *     // If you don't have any more mechanisms, authentication failed.
 *     if(mechanism == NULL) {
 *         // Terminate connection.
 *     }
 *
 *     // Tell the server you would like to try another mechanism with the
 *     // value stored in mechanism.
 * } else if(res == HASL_MECHANISM_RESULT_SUCCESS) {
 *     // Authentication complete.
 *
 *     // Optionally you can call done on the context to reset it for reuse
 *     // later.
 *     hasl_context_done(context);
 * } else if(res == HASL_MECHANISM_CONTINUE) {
 *     // Otherwise send client_out to the server.
 * }
 *
 * g_free(client_out);
 * ```
 *
 * Since: 0.1
 */
#define HASL_TYPE_CONTEXT (hasl_context_get_type())

HASL_AVAILABLE_IN_0_1
G_DECLARE_FINAL_TYPE(HaslContext, hasl_context, HASL, CONTEXT, GObject)

#include <hasl/haslmechanism.h>

/**
 * hasl_context_done:
 * @ctx: the instance
 *
 * Sets the context to done and resets it.
 *
 * This should be called after after you're done with the context, but might
 * want to use it again later to negotiate SASL again.
 *
 * An example of this is when services on IRC go down and come back up. After
 * your initial login, we would mark the context as done so that you can reuse
 * it when they come back. Of course marking it as done after that negotiation
 * as well.
 *
 * Since: 0.4
 */
HASL_AVAILABLE_IN_0_4
void hasl_context_done(HaslContext *ctx);

/**
 * hasl_context_new:
 *
 * Creates a new context.
 *
 * Returns: (transfer full): The new context.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
HaslContext *hasl_context_new(void);

/**
 * hasl_context_get_allowed_mechanisms:
 * @ctx: The instance.
 *
 * Gets the list of allowed mechanisms for @ctx.
 *
 * Returns: The list of allowed mechanisms.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
const char *hasl_context_get_allowed_mechanisms(HaslContext *ctx);

/**
 * hasl_context_set_allowed_mechanisms:
 * @ctx: the instance
 * @mechanisms: (nullable): the new list of mechanisms
 *
 * Sets the list of mechanism that are allowed for @ctx.
 *
 * @mechanisms can be separated by white space, commas, or a combination of
 * both.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
void hasl_context_set_allowed_mechanisms(HaslContext *ctx, const char *mechanisms);

/**
 * hasl_context_get_authzid:
 * @ctx: The instance.
 *
 * Gets the authzid of @ctx. See [property@Hasl.Context:authzid] for more
 * information.
 *
 * Returns: (nullable): The authzid if one is set, otherwise %NULL.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
const char *hasl_context_get_authzid(HaslContext *ctx);

/**
 * hasl_context_set_authzid:
 * @ctx: The instance.
 * @authzid: (nullable): The new authzid.
 *
 * Sets the authzid of @ctx to @authzid. See [property@Hasl.Context:authzid]
 * for more information.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
void hasl_context_set_authzid(HaslContext *ctx, const char *authzid);

/**
 * hasl_context_get_username:
 * @ctx: The instance.
 *
 * Gets the username that has been set on @ctx.
 *
 * Returns: (nullable): The username if set, otherwise %NULL.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
const char *hasl_context_get_username(HaslContext *ctx);

/**
 * hasl_context_set_username:
 * @ctx: The instance.
 * @username: (nullable): The new username.
 *
 * Sets the username of @ctx to @username.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
void hasl_context_set_username(HaslContext *ctx, const char *username);

/**
 * hasl_context_get_password:
 * @ctx: The instance.
 *
 * Gets the password from @ctx.
 *
 * Returns: (nullable): The password if set, otherwise %NULL.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
const char *hasl_context_get_password(HaslContext *ctx);

/**
 * hasl_context_set_password:
 * @ctx: The instance.
 * @password: (nullable): The new password.
 *
 * Sets the password for @ctx to @password.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
void hasl_context_set_password(HaslContext *ctx, const char *password);

/**
 * hasl_context_get_tls:
 * @ctx: The instance.
 *
 * Gets whether or not @ctx is working with a TLS connection.
 *
 * See [property@Hasl.Context:tls] for more information.
 *
 * Returns: %TRUE if @ctx is on a TLS connection, otherwise %FALSE.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
gboolean hasl_context_get_tls(HaslContext *ctx);

/**
 * hasl_context_set_tls:
 * @ctx: The instance.
 * @tls: Whether or not @ctx is working with a TLS connection.
 *
 * Sets whether or not @ctx is working with a TLS connection to @tls.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
void hasl_context_set_tls(HaslContext *ctx, gboolean tls);

/**
 * hasl_context_get_allow_clear_text:
 * @ctx: The instance.
 *
 * Gets whether or not it's okay to use mechanisms that use clear text with a
 * non-TLS connection. Clear text methods are ones that use credentials
 * directly like the `PLAIN` mechanism which depend on TLS to secure the
 * credentials.
 *
 * See [property@Hasl.Context:allow-clear-text] for more information.
 *
 * Returns: %TRUE if clear text methods are allowed.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
gboolean hasl_context_get_allow_clear_text(HaslContext *ctx);

/**
 * hasl_context_set_allow_clear_text:
 * @ctx: The instance.
 * @allow_clear_text: Whether or not to allow the mechanisms that depend on
 *                    clear text.
 *
 * Sets whether or not mechanisms that depend on clear text credentials are
 * allowed. The `PLAIN` mechanism is an example of a clear text based mechanism
 * as it depends on TLS to secure the credentials.
 *
 * See [property@Hasl.Context:allow-clear-text] for more information.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
void hasl_context_set_allow_clear_text(HaslContext *ctx, gboolean allow_clear_text);

/**
 * hasl_context_add_mechanism:
 * @ctx: The instance.
 * @name: The name of the mechanism.
 * @type: The type of the mechanism.
 *
 * Registers @type in @ctx with the given @name. @name should follow the
 * convention from
 * [RFC4422§3.1](https://www.rfc-editor.org/rfc/rfc4422#section-3.1).
 *
 * @type is the [type@GObject.Type] of the [class@Hasl.Mechanism] subclass.
 *
 * See the following example:
 *
 * ```c
 * hasl_context_add_mechanism(ctx, "PLAIN", HASL_TYPE_MECHANISM_PLAIN);
 * ```
 *
 * This only needs to be called for custom mechanisms, all of the built in
 * mechanism are already added to all contexts by default. However, you may
 * replace them by name if you need to adapt to a server that isn't RFC
 * compliant.
 *
 * Returns: %TRUE if the mechanism was added, otherwise %FALSE.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
gboolean hasl_context_add_mechanism(HaslContext *ctx, const char *name, GType type);

/**
 * hasl_context_get_supported_mechanisms:
 * @ctx: The instance.
 *
 * Gets a space separated alphabetically sorted list of all the mechanisms that
 * @ctx knows about.
 *
 * Returns: (transfer full): The list of mechanisms.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
char *hasl_context_get_supported_mechanisms(HaslContext *ctx);

/**
 * hasl_context_get_current_mechanism:
 * @ctx: The instance.
 *
 * Gets the name of the current mechanism that @ctx is attempting.
 *
 * If [method@hasl.Context.next] has not yet been called or if all mechanisms
 * in [property@hasl.Context:allowed-mechanisms] have been exhausted then %NULL
 * will be returned.
 *
 * Returns: The name of the current mechanism or %NULL.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
const char *hasl_context_get_current_mechanism(HaslContext *ctx);

/**
 * hasl_context_get_server_mechanisms:
 * @ctx: the instance
 *
 * Gets the server mechanisms from a context.
 *
 * Returns: (transfer none) (nullable): The mechanisms that the server
 *          advertised.
 *
 * Since: 0.4
 */
HASL_AVAILABLE_IN_0_4
const char *hasl_context_get_server_mechanisms(HaslContext *ctx);

/**
 * hasl_context_set_server_mechanisms:
 * @ctx: the instance
 * @mechanisms: (nullable): the new mechanisms
 *
 * Sets the mechanisms that the server has advertised.
 *
 * These mechanisms will be used if [property@Context:allowed-mechanisms] is
 * not set.
 *
 * @mechanisms can be separated by white space, commas, or a combination of
 * both.
 *
 * Since: 0.4
 */
HASL_AVAILABLE_IN_0_4
void hasl_context_set_server_mechanisms(HaslContext *ctx, const char *mechanisms);

/**
 * hasl_context_next:
 * @ctx: the instance
 *
 * Ask the context to move to the next possible mechanism.
 *
 * If @ctx is in a new or reset state via [method@Context.done], it will build
 * a list of mechanisms to attempt. It will prefer
 * [property@Context:allowed-mechanisms] over
 * [property@Context:server-mechanisms], falling back to
 * [method@Context.get_supported_mechanisms] if necessary.
 *
 * The context will create an instance of the next mechanism to try which it
 * will use internally and return the name that should be given to the server.
 *
 * Returns: (nullable): The name of the next mechanism to try or %NULL if all
 *          mechanisms have been exhausted.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
const char *hasl_context_next(HaslContext *ctx);

/**
 * hasl_context_step:
 * @ctx: The instance.
 * @server_in: (nullable): Input from the server.
 * @server_in_length: The length of @server_in in bytes.
 * @client_out: (transfer full): The data that the client should use.
 * @client_out_length: (out) (optional): The length of @client_out in bytes.
 * @error: (out): A return address for an error.
 *
 * Calls [method@Hasl.Mechanism.step] for the currently active mechanism in
 * @ctx.
 *
 * If there is no active mechanism because [method@Hasl.Context.next] has not
 * been called or all mechanisms have been exhausted this will return
 * [enum@Hasl.MechanismResult.ERROR] with @error set.
 *
 * Otherwise the return value depends on the current state of the mechanism
 * that is being attempted.
 *
 * Returns: a [enum@Hasl.MechanismResult] on how to proceed.
 *
 * Since: 0.1
 */
HASL_AVAILABLE_IN_0_1
HaslMechanismResult hasl_context_step(HaslContext *ctx, const guint8 *server_in, gsize server_in_length, guint8 **client_out, gsize *client_out_length, GError **error);

G_END_DECLS

#endif /* HASL_CONTEXT_H */
