/*
	Copyright (c) 2010 by Dennis Schridde

	This file is part of dovecot-metadata.

	dovecot-metadata 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 3 of the License, or
	(at your option) any later version.

	dovecot-metadata 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 dovecot-metadata.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "metadata-backend.h"

#include "dict.h"
#include "mail-storage.h"

#include <string.h>

#include "str-ext.h"
#include "dict-ext.h"
#include "mailbox-ext.h"
#include "metadata-entry-private.h"
#include "metadata-mail-user-module-private.h"

static const char *
dict_subjects[ENTRY_SUBJECT_MAX+1] = {
	"server/", /* ENTRY_SUBJECT_SERVER */
	"mailbox/", /* ENTRY_SUBJECT_MAILBOX */
	NULL
};

const char *
metadata_error_tostring(enum metadata_error error) {
	switch (error) {
		case METADATA_ERROR_NONE:
			return "No error";
		case METADATA_ERROR_UNKNOWN:
			return "Unknown error";
		case METADATA_ERROR_INVALID:
			return "Invalid entry";
		case METADATA_ERROR_TOOLARGE:
			return "Entry too large";
		case METADATA_ERROR_TOOMANY:
			return "Too many entries";
	}

	return "";
}

static const char*
t_dictsubject_from_entry(struct metadata_entry *entry) {
	switch (metadata_entry_get_subject(entry)) {
		case ENTRY_SUBJECT_SERVER:
			return dict_subjects[ENTRY_SUBJECT_SERVER];
		case ENTRY_SUBJECT_MAILBOX:
			return t_strconcat(dict_subjects[ENTRY_SUBJECT_MAILBOX], entry->mailbox_guid, "/", NULL);
		case ENTRY_SUBJECT_MAX:
			return NULL;
	}

	return NULL;
}

static const char*
t_dictkey_from_entry(struct metadata_entry *entry) {
	const char *subject = t_dictsubject_from_entry(entry);
	if (subject == NULL)
		return NULL;

	const char *path_prefix = NULL;
	switch (metadata_entry_get_scope(entry)) {
		case ENTRY_SCOPE_SHARED:
			path_prefix = DICT_PATH_SHARED;
			break;
		case ENTRY_SCOPE_PRIVATE:
			path_prefix = DICT_PATH_PRIVATE;
			break;
		case ENTRY_SCOPE_INVALID:
		case ENTRY_SCOPE_NONE:
			return NULL;
	}

	return t_strconcat(path_prefix, subject, &entry->name[1], NULL);
}

static int
count_entries(struct metadata_mail_user *muser) {
	struct dict_iterate_context *iter;
	const char *key;
	const char *value;
	int num = 0;

	iter = dict_iterate_init(muser->dict, DICT_PATH_SHARED, DICT_ITERATE_FLAG_RECURSE);
	while (dict_iterate(iter, &key, &value)) {
		num++;
	}
	if (dict_iterate_deinit(&iter) < 0) {
		i_error("metadata: dict iteration failed, can't count shared entries");
		return -1;
	}
	iter = dict_iterate_init(muser->dict, DICT_PATH_PRIVATE, DICT_ITERATE_FLAG_RECURSE);
	while (dict_iterate(iter, &key, &value)) {
		num++;
	}
	if (dict_iterate_deinit(&iter) < 0) {
		i_error("metadata: dict iteration failed, can't count private entries");
		return -1;
	}

	return num;
}

int
metadata_set_entry(struct metadata_entry *entry, struct mail_user *user) {
	struct metadata_mail_user *muser = METADATA_USER_CONTEXT(user);
	if (muser == NULL) {
		i_error("metadata: found NULL user, can't set their metadata");
		return -1;
	}

/* FIXME Currently all metadata is visible and editable by everyone! */
/* FIXME Respect section 3.3 of the RFC (ACL, mailbox permissions, quota) */

	if (!metadata_entry_is_valid(entry))
		return -METADATA_ERROR_INVALID;
	if (strlen(metadata_entry_get_value(entry)) > muser->set->maxsize)
		return -METADATA_ERROR_TOOLARGE;
	if (count_entries(muser) > muser->set->maxentries)
		return -METADATA_ERROR_TOOMANY;

	const char *key = t_dictkey_from_entry(entry);
	if (key == NULL)
		return -1;

	struct dict_transaction_context *dt = dict_transaction_begin(muser->dict);

	if (entry->value == NULL)
		dict_unset(dt, key);
	else
		dict_set(dt, key, entry->value);

	if (dict_transaction_commit(&dt) < 0) {
		i_error("metadata: dict commit failed");
		return -1;
	}

	return 0;
}

int
metadata_get_entry(struct metadata_entry *entry, struct mail_user *user) {
	struct metadata_mail_user *muser = METADATA_USER_CONTEXT(user);
	if (muser == NULL) {
		i_error("metadata: found NULL user, can't get their metadata");
		return -1;
	}

	if (!metadata_entry_is_valid(entry))
		return -METADATA_ERROR_INVALID;

	const char *key = t_dictkey_from_entry(entry);
	if (key == NULL)
		return -1;

	return dict_lookup(muser->dict, user->pool, key, &entry->value);
}

struct metadata_iterate_context {
	struct dict_iterate_multiscope_context *dict_ctx;
	unsigned int depth;
	bool failed;
};

struct metadata_iterate_context*
metadata_iterate_init(struct mailbox *mailbox, struct metadata_entry *entry, unsigned int depth) {
	struct metadata_iterate_context *ctx = i_new(struct metadata_iterate_context, 1);
	memset(ctx, 0, sizeof(*ctx));

	i_assert(entry != NULL);
	if (entry == NULL) {
		i_error("metadata: Received NULL entry, can't iterate over it");
		ctx->failed = true;
		return ctx;
	}

	struct mail_storage *storage = mailbox_get_storage(mailbox);
	struct mail_user *user = mail_storage_get_user(storage);
	struct metadata_mail_user *muser = METADATA_USER_CONTEXT(user);
	if (muser == NULL) {
		i_error("metadata: Found NULL user, can't iterate over their metadata");
		ctx->failed = true;
		return ctx;
	}

	enum dict_iterate_multiscope_flags flags = 0;
	if (depth != 0)
		flags |= DICT_ITERATE_FLAG_RECURSE;

	switch (metadata_entry_get_scope(entry)) {
		case ENTRY_SCOPE_SHARED:
		case ENTRY_SCOPE_PRIVATE:
			break;
		case ENTRY_SCOPE_INVALID:
			i_debug("metadata: Invalid scope for '%s' to dictkey", metadata_entry_get_name(entry));
			ctx->failed = true;
			return ctx;
		case ENTRY_SCOPE_NONE:
			flags |= DICT_ITERATE_MULTISCOPE_FLAG_MULTISCOPE;
			break;
	}

	const char *key = t_dictkey_from_entry(entry);
	if (key == NULL) {
		i_debug("metadata: Unable to translate '%s' to dictkey, can't iterate over it", metadata_entry_get_name(entry));
		ctx->failed = true;
		return ctx;
	}

	const unsigned int root_depth = strchr_num(key, '/');
	ctx->depth = root_depth + depth;

	ctx->dict_ctx = dict_iterate_multiscope_init(muser->dict, key, flags);
	if (ctx->dict_ctx == NULL) {
		i_debug("metadata: Initialising iteration over '%s' failed", metadata_entry_get_name(entry));
		ctx->failed = true;
		return ctx;
	}

	return ctx;
}

static ATTR_NONNULL(2)
const char *
entry_name_from_dict_name(enum metadata_entry_subject subject, const char *dict_name) {
	/* skip dict internal prefixes: priv/ or shared/ */
	const char *name_after_scope = strchr(dict_name, '/');
	if (name_after_scope == NULL) {
		return NULL;
	}

	/* skip '/' */
	name_after_scope++;

	/* skip dict internal prefixes: server/ or mailbox/ */
	const char *name_after_subject = strchr(name_after_scope, '/');
	if (name_after_subject == NULL) {
		return NULL;
	}

	/* do not skip '/', the name needs to start with a '/'! */

	/* skip dict internal prefixes: <mailbox_guid>/ (for mailboxes only) */
	if (subject == ENTRY_SUBJECT_MAILBOX) {
		name_after_subject = strchr(name_after_subject+1, '/');
		if (name_after_subject == NULL) {
			return NULL;
		}
	}

	return name_after_subject;
}

bool
metadata_iterate(struct metadata_iterate_context *ctx, struct metadata_entry *entry) {
	i_assert(ctx != NULL);
	if (ctx == NULL)
		return false;

	if (ctx->failed)
		return false;

	/* Clear entry */
	entry->name = NULL;
	entry->value = NULL;

	while (entry->name == NULL) {
		const char *dict_name = NULL, *dict_value = NULL;
		if (!dict_iterate_multiscope(ctx->dict_ctx, &dict_name, &dict_value))
			return false;

		if (strchr_num(dict_name, '/') > ctx->depth)
			continue;

		const char *entry_name = entry_name_from_dict_name(metadata_entry_get_subject(entry), dict_name);
		if (entry_name == NULL) {
			i_debug("metadata: Unable to translate '%s' to entry name", dict_name);
			ctx->failed = true;
			return false;
		}

		entry->name = i_strdup(entry_name);
		entry->value = i_strdup(dict_value);
	}

	return true;
}

int
metadata_iterate_deinit(struct metadata_iterate_context **ctx) {
	i_assert(ctx != NULL);
	if (ctx == NULL)
		return -1;

	i_assert(*ctx != NULL);
	if (*ctx == NULL)
		return -1;

	int ret = (*ctx)->failed ? -1 : 0;

	if ((*ctx)->dict_ctx != NULL && dict_iterate_multiscope_deinit(&(*ctx)->dict_ctx) < 0)
		ret = -1;

	i_free(*ctx);

	return ret;
}
