/*
 * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#include "config.h"

#include "jcat-common-private.h"
#include "jcat-context-private.h"
#include "jcat-engine-private.h"

typedef struct {
	JcatContext *context; /* weak */
	JcatBlobKind kind;
	JcatBlobMethod method;
	gboolean done_setup;
} JcatEnginePrivate;

G_DEFINE_TYPE_WITH_PRIVATE(JcatEngine, jcat_engine, G_TYPE_OBJECT)
#define GET_PRIVATE(o) (jcat_engine_get_instance_private(o))

enum { PROP_0, PROP_CONTEXT, PROP_KIND, PROP_METHOD, PROP_VERIFY_KIND, PROP_LAST };

static const gchar *
jcat_engine_method_to_string(JcatBlobMethod method)
{
	if (method == JCAT_BLOB_METHOD_CHECKSUM)
		return "checksum";
	if (method == JCAT_BLOB_METHOD_SIGNATURE)
		return "signature";
	return NULL;
}

/* private */
void
jcat_engine_add_string(JcatEngine *self, guint idt, GString *str)
{
	JcatEnginePrivate *priv = GET_PRIVATE(self);
	jcat_string_append_kv(str, idt, G_OBJECT_TYPE_NAME(self), NULL);
	jcat_string_append_kv(str, idt + 1, "Kind", jcat_blob_kind_to_string(priv->kind));
	jcat_string_append_kv(str,
			      idt + 1,
			      "VerifyKind",
			      jcat_engine_method_to_string(priv->method));
}

/* private */
gchar *
jcat_engine_to_string(JcatEngine *self)
{
	GString *str = g_string_new(NULL);
	jcat_engine_add_string(self, 0, str);
	return g_string_free(str, FALSE);
}

static gboolean
jcat_engine_setup(JcatEngine *self, GError **error)
{
	JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self);
	JcatEnginePrivate *priv = GET_PRIVATE(self);

	g_return_val_if_fail(JCAT_IS_ENGINE(self), FALSE);

	/* already done */
	if (priv->done_setup)
		return TRUE;

	/* optional */
	if (klass->setup != NULL) {
		if (!klass->setup(self, error))
			return FALSE;
	}
	if (klass->add_public_key != NULL) {
		GPtrArray *fns = jcat_context_get_public_keys(priv->context);
		for (guint i = 0; i < fns->len; i++) {
			const gchar *fn = g_ptr_array_index(fns, i);
			if (!klass->add_public_key(self, fn, error))
				return FALSE;
		}
	}

	/* success */
	priv->done_setup = TRUE;
	return TRUE;
}

/**
 * jcat_engine_pubkey_verify:
 * @self: #JcatEngine
 * @blob: #GBytes
 * @blob_signature: #GBytes
 * @flags: #JcatVerifyFlags, e.g. %JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS
 * @error: #GError, or %NULL
 *
 * Verifies a chunk of data.
 *
 * Returns: (transfer full): #JcatResult, or %NULL for failed
 *
 * Since: 0.1.0
 **/
JcatResult *
jcat_engine_pubkey_verify(JcatEngine *self,
			  GBytes *blob,
			  GBytes *blob_signature,
			  JcatVerifyFlags flags,
			  GError **error)
{
	JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self);
	g_return_val_if_fail(JCAT_IS_ENGINE(self), NULL);
	g_return_val_if_fail(blob != NULL, NULL);
	g_return_val_if_fail(blob_signature != NULL, NULL);
	if (klass->pubkey_verify == NULL) {
		g_set_error_literal(error,
				    G_IO_ERROR,
				    G_IO_ERROR_NOT_SUPPORTED,
				    "verifying data is not supported");
		return NULL;
	}
	if (!jcat_engine_setup(self, error))
		return NULL;
	return klass->pubkey_verify(self, blob, blob_signature, flags, error);
}

/**
 * jcat_engine_pubkey_sign:
 * @self: #JcatEngine
 * @blob: #GBytes
 * @cert: #GBytes
 * @privkey: #GBytes
 * @flags: #JcatSignFlags, e.g. %JCAT_SIGN_FLAG_ADD_TIMESTAMP
 * @error: #GError, or %NULL
 *
 * Signs a chunk of data.
 *
 * Returns: (transfer full): #JcatBlob, or %NULL for failed
 *
 * Since: 0.1.0
 **/
JcatBlob *
jcat_engine_pubkey_sign(JcatEngine *self,
			GBytes *blob,
			GBytes *cert,
			GBytes *privkey,
			JcatSignFlags flags,
			GError **error)
{
	JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self);
	g_return_val_if_fail(JCAT_IS_ENGINE(self), NULL);
	g_return_val_if_fail(blob != NULL, NULL);
	g_return_val_if_fail(cert != NULL, NULL);
	g_return_val_if_fail(privkey != NULL, NULL);
	if (klass->pubkey_sign == NULL) {
		g_set_error_literal(error,
				    G_IO_ERROR,
				    G_IO_ERROR_NOT_SUPPORTED,
				    "signing data is not supported");
		return NULL;
	}
	if (!jcat_engine_setup(self, error))
		return NULL;
	return klass->pubkey_sign(self, blob, cert, privkey, flags, error);
}

/**
 * jcat_engine_self_verify:
 * @self: #JcatEngine
 * @blob: #GBytes
 * @blob_signature: #GBytes
 * @flags: #JcatVerifyFlags, e.g. %JCAT_VERIFY_FLAG_DISABLE_TIME_CHECKS
 * @error: #GError, or %NULL
 *
 * Verifies a chunk of data.
 *
 * Returns: (transfer full): #JcatResult, or %NULL for failed
 *
 * Since: 0.1.0
 **/
JcatResult *
jcat_engine_self_verify(JcatEngine *self,
			GBytes *blob,
			GBytes *blob_signature,
			JcatVerifyFlags flags,
			GError **error)
{
	JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self);
	g_return_val_if_fail(JCAT_IS_ENGINE(self), NULL);
	g_return_val_if_fail(blob != NULL, NULL);
	g_return_val_if_fail(blob_signature != NULL, NULL);
	if (klass->self_verify == NULL) {
		g_set_error_literal(error,
				    G_IO_ERROR,
				    G_IO_ERROR_NOT_SUPPORTED,
				    "verifying data is not supported");
		return NULL;
	}
	if (!jcat_engine_setup(self, error))
		return NULL;
	return klass->self_verify(self, blob, blob_signature, flags, error);
}

/**
 * jcat_engine_self_sign:
 * @self: #JcatEngine
 * @blob: #GBytes
 * @flags: #JcatSignFlags, e.g. %JCAT_SIGN_FLAG_ADD_TIMESTAMP
 * @error: #GError, or %NULL
 *
 * Signs a chunk of data.
 *
 * Returns: (transfer full): #JcatBlob, or %NULL for failed
 *
 * Since: 0.1.0
 **/
JcatBlob *
jcat_engine_self_sign(JcatEngine *self, GBytes *blob, JcatSignFlags flags, GError **error)
{
	JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self);
	g_return_val_if_fail(JCAT_IS_ENGINE(self), NULL);
	g_return_val_if_fail(blob != NULL, NULL);
	if (klass->self_sign == NULL) {
		g_set_error_literal(error,
				    G_IO_ERROR,
				    G_IO_ERROR_NOT_SUPPORTED,
				    "signing data is not supported");
		return NULL;
	}
	if (!jcat_engine_setup(self, error))
		return NULL;
	return klass->self_sign(self, blob, flags, error);
}

/**
 * jcat_engine_add_public_key_raw:
 * @self: #JcatEngine
 * @blob: #GBytes
 * @error: #GError, or %NULL
 *
 * Adds a public key manually.
 *
 * Returns: %
 *
 * Since: 0.1.9
 **/
gboolean
jcat_engine_add_public_key_raw(JcatEngine *self, GBytes *blob, GError **error)
{
	JcatEngineClass *klass = JCAT_ENGINE_GET_CLASS(self);
	g_return_val_if_fail(JCAT_IS_ENGINE(self), FALSE);
	g_return_val_if_fail(blob != NULL, FALSE);
	if (klass->add_public_key_raw == NULL) {
		g_set_error_literal(error,
				    G_IO_ERROR,
				    G_IO_ERROR_NOT_SUPPORTED,
				    "adding public keys manually is not supported");
		return FALSE;
	}
	if (!jcat_engine_setup(self, error))
		return FALSE;
	return klass->add_public_key_raw(self, blob, error);
}

/**
 * jcat_engine_get_kind:
 * @self: #JcatEngine
 *
 * Gets the blob kind.
 *
 * Returns: #JcatBlobKind, e.g. %JCAT_BLOB_KIND_SHA256
 *
 * Since: 0.1.3
 **/
JcatBlobKind
jcat_engine_get_kind(JcatEngine *self)
{
	JcatEnginePrivate *priv = GET_PRIVATE(self);
	return priv->kind;
}

/**
 * jcat_engine_get_method:
 * @self: #JcatEngine
 *
 * Gets the verification method.
 *
 * Returns: #JcatBlobMethod, e.g. %JCAT_BLOB_METHOD_SIGNATURE
 *
 * Since: 0.1.3
 **/
JcatBlobMethod
jcat_engine_get_method(JcatEngine *self)
{
	JcatEnginePrivate *priv = GET_PRIVATE(self);
	return priv->method;
}

const gchar *
jcat_engine_get_keyring_path(JcatEngine *self)
{
	JcatEnginePrivate *priv = GET_PRIVATE(self);
	if (priv->context == NULL)
		return NULL;
	return jcat_context_get_keyring_path(priv->context);
}

static void
jcat_engine_finalize(GObject *object)
{
	G_OBJECT_CLASS(jcat_engine_parent_class)->finalize(object);
}

static void
jcat_engine_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
	JcatEngine *self = JCAT_ENGINE(object);
	JcatEnginePrivate *priv = GET_PRIVATE(self);
	switch (prop_id) {
	case PROP_CONTEXT:
		g_value_set_object(value, priv->context);
		break;
	case PROP_KIND:
		g_value_set_uint(value, priv->kind);
		break;
	case PROP_METHOD:
	case PROP_VERIFY_KIND:
		g_value_set_uint(value, priv->method);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
jcat_engine_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
	JcatEngine *self = JCAT_ENGINE(object);
	JcatEnginePrivate *priv = GET_PRIVATE(self);
	switch (prop_id) {
	case PROP_CONTEXT:
		/* weak */
		priv->context = g_value_get_object(value);
		break;
	case PROP_KIND:
		priv->kind = g_value_get_uint(value);
		break;
	case PROP_METHOD:
	case PROP_VERIFY_KIND:
		priv->method = g_value_get_uint(value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
jcat_engine_class_init(JcatEngineClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);
	GParamSpec *pspec;

	object_class->get_property = jcat_engine_get_property;
	object_class->set_property = jcat_engine_set_property;

	pspec =
	    g_param_spec_object("context",
				NULL,
				NULL,
				JCAT_TYPE_CONTEXT,
				G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME);
	g_object_class_install_property(object_class, PROP_CONTEXT, pspec);

	pspec = g_param_spec_uint("kind",
				  NULL,
				  NULL,
				  0,
				  G_MAXUINT,
				  0,
				  G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME);
	g_object_class_install_property(object_class, PROP_KIND, pspec);

	pspec = g_param_spec_uint("method",
				  NULL,
				  NULL,
				  JCAT_BLOB_METHOD_UNKNOWN,
				  JCAT_BLOB_METHOD_LAST,
				  JCAT_BLOB_METHOD_UNKNOWN,
				  G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME);
	g_object_class_install_property(object_class, PROP_METHOD, pspec);

	/* this is the old name to preserve GObject ABI */
	pspec = g_param_spec_uint("verify-kind",
				  NULL,
				  NULL,
				  JCAT_BLOB_METHOD_UNKNOWN,
				  JCAT_BLOB_METHOD_LAST,
				  JCAT_BLOB_METHOD_UNKNOWN,
				  G_PARAM_READABLE | G_PARAM_DEPRECATED | G_PARAM_STATIC_NAME);
	g_object_class_install_property(object_class, PROP_VERIFY_KIND, pspec);
	object_class->finalize = jcat_engine_finalize;
}

static void
jcat_engine_init(JcatEngine *self)
{
}
