/*
 * Copyright (C) 2013 Red Hat Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above
 *       copyright notice, this list of conditions and the
 *       following disclaimer.
 *     * Redistributions in binary form must reproduce the
 *       above copyright notice, this list of conditions and
 *       the following disclaimer in the documentation and/or
 *       other materials provided with the distribution.
 *     * The names of contributors to this software may not be
 *       used to endorse or promote products derived from this
 *       software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * Author: Stef Walter <stefw@redhat.com>
 */

#include "compat.h"

#define P11_DEBUG_FLAG P11_DEBUG_TRUST

#include "attrs.h"
#include "debug.h"
#include "dict.h"
#include "index.h"
#include "module.h"

#include <assert.h>
#include <stdlib.h>

/*
 * TODO: Eventually we want to be using a bloom filter to optimize and
 * actually implement the index.
 */

struct _p11_index {
	/* The list of objects */
	p11_dict *objects;

	/* Data passed to callbacks */
	void *data;

	/* Called to build an new/modified object */
	p11_index_build_cb build;

	/* Called after objects change */
	p11_index_changed_cb changed;

	/* Used for queueing changes, when in a batch */
	p11_dict *changes;
	bool changing;
};

struct object {
	CK_OBJECT_HANDLE handle;
	CK_ATTRIBUTE *attrs;
};

static void
free_object (void *data)
{
	struct object *obj = data;
	p11_attrs_free (obj->attrs);
	free (obj);
}

p11_index *
p11_index_new (p11_index_build_cb build,
               p11_index_changed_cb changed,
               void *data)
{
	p11_index *index;

	index = calloc (1, sizeof (p11_index));
	return_val_if_fail (index != NULL, NULL);

	index->build = build;
	index->changed = changed;
	index->data = data;

	index->objects = p11_dict_new (p11_dict_ulongptr_hash,
	                               p11_dict_ulongptr_equal,
	                               NULL, free_object);
	return_val_if_fail (index->objects != NULL, NULL);

	return index;
}

void
p11_index_free (p11_index *index)
{
	return_if_fail (index != NULL);

	p11_dict_free (index->objects);
	p11_dict_free (index->changes);
	free (index);
}

int
p11_index_size (p11_index *index)
{
	return_val_if_fail (index != NULL, -1);
	return p11_dict_size (index->objects);
}

static CK_RV
index_build (p11_index *index,
             CK_ATTRIBUTE **attrs,
             CK_ATTRIBUTE *merge)
{
	if (index->build) {
		return index->build (index->data, index, attrs, merge);
	} else {
		*attrs = p11_attrs_merge (*attrs, merge, true);
		return CKR_OK;
	}
}

static void
call_change (p11_index *index,
             CK_OBJECT_HANDLE handle,
             CK_ATTRIBUTE *attrs)
{
	assert (index->changed);

	/* When attrs is NULL, means this is a modify */
	if (attrs == NULL) {
		attrs = p11_index_lookup (index, handle);
		if (attrs == NULL)
			return;

	/* Otherwise a remove operation, handle not valid anymore */
	} else {
		handle = 0;
	}

	index->changing = true;
	index->changed (index->data, index, handle, attrs);
	index->changing = false;
}

static void
index_change (p11_index *index,
              CK_OBJECT_HANDLE handle,
              CK_ATTRIBUTE *removed)
{
	struct object *obj;

	if (!index->changed || index->changing)
		return;

	if (!index->changes) {
		call_change (index, handle, removed);
		p11_attrs_free (removed);

	} else {
		obj = calloc (1, sizeof (struct object));
		return_if_fail (obj != NULL);

		obj->handle = handle;
		obj->attrs = removed;
		if (!p11_dict_set (index->changes, &obj->handle, obj))
			return_if_reached ();
	}
}

void
p11_index_batch (p11_index *index)
{
	return_if_fail (index != NULL);

	if (index->changes)
		return;

	index->changes = p11_dict_new (p11_dict_ulongptr_hash,
	                               p11_dict_ulongptr_equal,
	                               NULL, free_object);
	return_if_fail (index->changes != NULL);
}

void
p11_index_finish (p11_index *index)
{
	p11_dict *changes;
	struct object *obj;
	p11_dictiter iter;

	return_if_fail (index != NULL);

	if (!index->changes)
		return;

	changes = index->changes;
	index->changes = NULL;

	p11_dict_iterate (changes, &iter);
	while (p11_dict_next (&iter, NULL, (void **)&obj))
		call_change (index, obj->handle, obj->attrs);

	p11_dict_free (changes);
}

bool
p11_index_in_batch (p11_index *index)
{
	return_val_if_fail (index != NULL, false);
	return index->changes ? true : false;
}

CK_RV
p11_index_take (p11_index *index,
                CK_ATTRIBUTE *attrs,
                CK_OBJECT_HANDLE *handle)
{
	struct object *obj;
	CK_RV rv;

	return_val_if_fail (index != NULL, CKR_GENERAL_ERROR);
	return_val_if_fail (attrs != NULL, CKR_GENERAL_ERROR);

	obj = calloc (1, sizeof (struct object));
	return_val_if_fail (obj != NULL, CKR_HOST_MEMORY);

	rv = index_build (index, &obj->attrs, attrs);
	if (rv != CKR_OK) {
		p11_attrs_free (attrs);
		return rv;
	}

	return_val_if_fail (obj->attrs != NULL, CKR_GENERAL_ERROR);
	obj->handle = p11_module_next_id ();

	if (!p11_dict_set (index->objects, &obj->handle, obj))
		return_val_if_reached (CKR_HOST_MEMORY);

	if (handle)
		*handle = obj->handle;

	index_change (index, obj->handle, NULL);
	return CKR_OK;
}

CK_RV
p11_index_add (p11_index *index,
               CK_ATTRIBUTE *attrs,
               CK_ULONG count,
               CK_OBJECT_HANDLE *handle)
{
	CK_ATTRIBUTE *copy;

	return_val_if_fail (index != NULL, CKR_GENERAL_ERROR);
	return_val_if_fail (attrs == NULL || count > 0, CKR_ARGUMENTS_BAD);

	copy = p11_attrs_buildn (NULL, attrs, count);
	return_val_if_fail (copy != NULL, CKR_HOST_MEMORY);

	return p11_index_take (index, copy, handle);
}

CK_RV
p11_index_update (p11_index *index,
                  CK_OBJECT_HANDLE handle,
                  CK_ATTRIBUTE *update)
{
	struct object *obj;
	CK_RV rv;

	return_val_if_fail (index != NULL, CKR_GENERAL_ERROR);
	return_val_if_fail (update != NULL, CKR_GENERAL_ERROR);

	obj = p11_dict_get (index->objects, &handle);
	if (obj == NULL) {
		p11_attrs_free (update);
		return CKR_OBJECT_HANDLE_INVALID;
	}

	rv = index_build (index, &obj->attrs, update);
	if (rv != CKR_OK) {
		p11_attrs_free (update);
		return rv;
	}

	index_change (index, obj->handle, NULL);
	return CKR_OK;
}

CK_RV
p11_index_set (p11_index *index,
               CK_OBJECT_HANDLE handle,
               CK_ATTRIBUTE *attrs,
               CK_ULONG count)
{
	CK_ATTRIBUTE *update;
	struct object *obj;

	return_val_if_fail (index != NULL, CKR_GENERAL_ERROR);

	obj = p11_dict_get (index->objects, &handle);
	if (obj == NULL)
		return CKR_OBJECT_HANDLE_INVALID;

	update = p11_attrs_buildn (NULL, attrs, count);
	return_val_if_fail (update != NULL, CKR_HOST_MEMORY);

	return p11_index_update (index, handle, update);
}

CK_RV
p11_index_remove (p11_index *index,
                  CK_OBJECT_HANDLE handle)
{
	struct object *obj;

	return_val_if_fail (index != NULL, CKR_GENERAL_ERROR);

	if (!p11_dict_steal (index->objects, &handle, NULL, (void **)&obj))
		return CKR_OBJECT_HANDLE_INVALID;

	/* This takes ownership of the attributes */
	index_change (index, handle, obj->attrs);
	obj->attrs = NULL;
	free_object (obj);

	return CKR_OK;
}

static CK_RV
index_replacev (p11_index *index,
                CK_ATTRIBUTE *match,
                CK_ATTRIBUTE_TYPE key,
                CK_ATTRIBUTE **replace,
                CK_ULONG replacen)
{
	CK_OBJECT_HANDLE *handles;
	struct object *obj;
	CK_ATTRIBUTE *attrs;
	CK_ATTRIBUTE *attr;
	bool handled = false;
	CK_RV rv;
	int i, j;

	handles = p11_index_find_all (index, match);

	for (i = 0; handles && handles[i] != 0; i++) {
		obj = p11_dict_get (index->objects, handles + i);
		if (obj == NULL)
			continue;

		handled = false;
		attr = p11_attrs_find (obj->attrs, key);

		/* The match doesn't have the key, so remove it */
		if (attr != NULL) {
			for (j = 0; j < replacen; j++) {
				if (!replace[j])
					continue;
				if (p11_attrs_matchn (replace[j], attr, 1)) {
					attrs = NULL;
					rv = index_build (index, &attrs, replace[j]);
					if (rv != CKR_OK)
						return rv;
					p11_attrs_free (obj->attrs);
					obj->attrs = attrs;
					replace[j] = NULL;
					handled = true;
					break;
				}
			}
		}

		if (!handled) {
			rv = p11_index_remove (index, handles[i]);
			if (rv != CKR_OK)
				return rv;
		}
	}

	for (j = 0; j < replacen; j++) {
		if (!replace[j])
			continue;
		rv = p11_index_take (index, replace[j], NULL);
		if (rv != CKR_OK)
			return rv;
		replace[j] = NULL;
	}

	free (handles);
	return CKR_OK;
}

CK_RV
p11_index_replace (p11_index *index,
                   CK_ATTRIBUTE *match,
                   CK_ATTRIBUTE_TYPE key,
                   CK_ATTRIBUTE *replace)
{
	return_val_if_fail (index != NULL, CKR_GENERAL_ERROR);
	return index_replacev (index, match, key, &replace,
	                       replace ? 1 : 0);
}

CK_RV
p11_index_replace_all (p11_index *index,
                       CK_ATTRIBUTE *match,
                       CK_ATTRIBUTE_TYPE key,
                       p11_array *replace)
{
	CK_RV rv;
	int i;

	return_val_if_fail (index != NULL, CKR_GENERAL_ERROR);

	rv = index_replacev (index, match, key,
	                     (CK_ATTRIBUTE **)replace->elem,
	                     replace->num);

	for (i = 0; i < replace->num; i++) {
		if (!replace->elem[i]) {
			p11_array_remove (replace, i);
			i--;
		}
	}

	return rv;
}

CK_ATTRIBUTE *
p11_index_lookup (p11_index *index,
                  CK_OBJECT_HANDLE handle)
{
	struct object *obj;

	return_val_if_fail (index != NULL, NULL);

	if (handle == CK_INVALID_HANDLE)
		return NULL;

	obj = p11_dict_get (index->objects, &handle);
	return obj ? obj->attrs : NULL;
}

CK_OBJECT_HANDLE
p11_index_find (p11_index *index,
                CK_ATTRIBUTE *match)
{
	struct object *obj;
	p11_dictiter iter;

	p11_dict_iterate (index->objects, &iter);
	while (p11_dict_next (&iter, NULL, (void *)&obj)) {
		if (p11_attrs_match (obj->attrs, match))
			return obj->handle;
	}

	return 0;
}

CK_OBJECT_HANDLE
p11_index_findn (p11_index *index,
                 CK_ATTRIBUTE *match,
                 CK_ULONG count)
{
	struct object *obj;
	p11_dictiter iter;

	p11_dict_iterate (index->objects, &iter);
	while (p11_dict_next (&iter, NULL, (void *)&obj)) {
		if (p11_attrs_matchn (obj->attrs, match, count))
			return obj->handle;
	}

	return 0;
}

CK_OBJECT_HANDLE *
p11_index_find_all (p11_index *index,
                    CK_ATTRIBUTE *match)
{
	CK_OBJECT_HANDLE *handles = NULL;
	struct object *obj;
	p11_dictiter iter;
	int nhandles;
	int at = 0;

	nhandles = 16;
	handles = malloc (nhandles * sizeof (CK_OBJECT_HANDLE));
	return_val_if_fail (handles != NULL, NULL);

	p11_dict_iterate (index->objects, &iter);
	while (p11_dict_next (&iter, NULL, (void *)&obj)) {
		if (p11_attrs_match (obj->attrs, match)) {
			if (at + 2 > nhandles) {
				nhandles += 16;
				handles = realloc (handles, nhandles * sizeof (CK_OBJECT_HANDLE));
				return_val_if_fail (handles != NULL, NULL);
			}
			handles[at++] = obj->handle;
		}
	}

	handles[at++] = 0UL;
	return handles;
}

CK_OBJECT_HANDLE *
p11_index_snapshot (p11_index *index,
                    p11_index *base,
                    CK_ATTRIBUTE *attrs,
                    CK_ULONG count)
{
	CK_OBJECT_HANDLE *snapshot;
	CK_OBJECT_HANDLE *handle;
	p11_dictiter iter;
	int num;
	int i;

	/*
	 * TODO: The concept is that we use our bloom filter to provide
	 * an initial rough snapshot here of which objects match, but for
	 * now just include everything in the snapshot.
	 */

	return_val_if_fail (index != NULL, NULL);

	num = p11_index_size (index) + 1;
	if (base)
		num += p11_index_size (base);

	snapshot = calloc (num, sizeof (CK_OBJECT_HANDLE));
	return_val_if_fail (snapshot != NULL, NULL);

	p11_dict_iterate (index->objects, &iter);
	for (i = 0 ; p11_dict_next (&iter, (void *)&handle, NULL); i++) {
		assert (i < num);
		snapshot[i] = *handle;
	}

	if (base) {
		p11_dict_iterate (base->objects, &iter);
		for ( ; p11_dict_next (&iter, (void *)&handle, NULL); i++) {
			assert (i < num);
			snapshot[i] = *handle;
		}
	}

	assert (i < num);
	assert (snapshot[i] == 0UL);

	return snapshot;
}