/* 
   ldb database library

   Copyright (C) Simo Sorce  2005-2008

     ** NOTE! The following LGPL license applies to the ldb
     ** library. This does NOT imply that all of Samba is released
     ** under the LGPL
   
   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 3 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 <http://www.gnu.org/licenses/>.
*/

/*
 *  Name: ldb
 *
 *  Component: ldb server side sort control module
 *
 *  Description: this module sorts the results of a search
 *
 *  Author: Simo Sorce
 */

#include "replace.h"
#include "system/filesys.h"
#include "system/time.h"
#include "ldb_module.h"

struct opaque {
	struct ldb_context *ldb;
	const struct ldb_attrib_handler *h;
	const char *attribute;
	int reverse;
	int result;
};

struct sort_context {
	struct ldb_module *module;

	const char *attributeName;
	const char *orderingRule;
	int reverse;

	struct ldb_request *req;
	struct ldb_message **msgs;
	char **referrals;
	unsigned int num_msgs;
	unsigned int num_refs;

	const struct ldb_schema_attribute *a;
	int sort_result;
};

static int build_response(void *mem_ctx, struct ldb_control ***ctrls, int result, const char *desc)
{
	struct ldb_control **controls;
	struct ldb_sort_resp_control *resp;
	unsigned int i;

	if (*ctrls) {
		controls = *ctrls;
		for (i = 0; controls[i]; i++);
		controls = talloc_realloc(mem_ctx, controls, struct ldb_control *, i + 2);
	} else {
		i = 0;
		controls = talloc_array(mem_ctx, struct ldb_control *, 2);
	}
	if (! controls )
		return LDB_ERR_OPERATIONS_ERROR;

	*ctrls = controls;

	controls[i+1] = NULL;
	controls[i] = talloc(controls, struct ldb_control);
	if (! controls[i] )
		return LDB_ERR_OPERATIONS_ERROR;

	controls[i]->oid = LDB_CONTROL_SORT_RESP_OID;
	controls[i]->critical = 0;

	resp = talloc(controls[i], struct ldb_sort_resp_control);
	if (! resp )
		return LDB_ERR_OPERATIONS_ERROR;

	resp->result = result;
	resp->attr_desc = talloc_strdup(resp, desc);

	if (! resp->attr_desc )
		return LDB_ERR_OPERATIONS_ERROR;
	
	controls[i]->data = resp;

	return LDB_SUCCESS;
}

static int sort_compare(struct ldb_message **msg1, struct ldb_message **msg2, void *opaque)
{
	struct sort_context *ac = talloc_get_type(opaque, struct sort_context);
	struct ldb_message_element *el1, *el2;
	struct ldb_context *ldb;

	ldb = ldb_module_get_ctx(ac->module);

	if (ac->sort_result != 0) {
		/* an error occurred previously,
		 * let's exit the sorting by returning always 0 */
		return 0;
	}

	el1 = ldb_msg_find_element(*msg1, ac->attributeName);
	el2 = ldb_msg_find_element(*msg2, ac->attributeName);

	if (!el1 && el2) {
		return 1;
	}
	if (el1 && !el2) {
		return -1;
	}
	if (!el1 && !el2) {
		return 0;
	}

	if (ac->reverse)
		return ac->a->syntax->comparison_fn(ldb, ac, &el2->values[0], &el1->values[0]);

	return ac->a->syntax->comparison_fn(ldb, ac, &el1->values[0], &el2->values[0]);
}

static int server_sort_results(struct sort_context *ac)
{
	struct ldb_context *ldb;
	struct ldb_reply *ares;
	unsigned int i;
	int ret;

	ldb = ldb_module_get_ctx(ac->module);

	ac->a = ldb_schema_attribute_by_name(ldb, ac->attributeName);
	ac->sort_result = 0;

	LDB_TYPESAFE_QSORT(ac->msgs, ac->num_msgs, ac, sort_compare);

	if (ac->sort_result != LDB_SUCCESS) {
		return ac->sort_result;
	}

	for (i = 0; i < ac->num_msgs; i++) {
		ares = talloc_zero(ac, struct ldb_reply);
		if (!ares) {
			return LDB_ERR_OPERATIONS_ERROR;
		}

		ares->type = LDB_REPLY_ENTRY;
		ares->message = talloc_move(ares, &ac->msgs[i]);

		ret = ldb_module_send_entry(ac->req, ares->message, ares->controls);
		if (ret != LDB_SUCCESS) {
			return ret;
		}
	}

	for (i = 0; i < ac->num_refs; i++) {
		ares = talloc_zero(ac, struct ldb_reply);
		if (!ares) {
			return LDB_ERR_OPERATIONS_ERROR;
		}

		ares->type = LDB_REPLY_REFERRAL;
		ares->referral = talloc_move(ares, &ac->referrals[i]);

		ret = ldb_module_send_referral(ac->req, ares->referral);
		if (ret != LDB_SUCCESS) {
			return ret;
		}
	}

	return LDB_SUCCESS;
}

static int server_sort_search_callback(struct ldb_request *req, struct ldb_reply *ares)
{
	struct sort_context *ac;
	struct ldb_context *ldb;
	int ret;

	ac = talloc_get_type(req->context, struct sort_context);
	ldb = ldb_module_get_ctx(ac->module);

	if (!ares) {
		return ldb_module_done(ac->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}
	if (ares->error != LDB_SUCCESS) {
		return ldb_module_done(ac->req, ares->controls,
					ares->response, ares->error);
	}

	switch (ares->type) {
	case LDB_REPLY_ENTRY:
		ac->msgs = talloc_realloc(ac, ac->msgs, struct ldb_message *, ac->num_msgs + 2);
		if (! ac->msgs) {
			talloc_free(ares);
			ldb_oom(ldb);
			return ldb_module_done(ac->req, NULL, NULL,
						LDB_ERR_OPERATIONS_ERROR);
		}

		ac->msgs[ac->num_msgs] = talloc_steal(ac->msgs, ares->message);
		ac->num_msgs++;
		ac->msgs[ac->num_msgs] = NULL;

		break;

	case LDB_REPLY_REFERRAL:
		ac->referrals = talloc_realloc(ac, ac->referrals, char *, ac->num_refs + 2);
		if (! ac->referrals) {
			talloc_free(ares);
			ldb_oom(ldb);
			return ldb_module_done(ac->req, NULL, NULL,
						LDB_ERR_OPERATIONS_ERROR);
		}

		ac->referrals[ac->num_refs] = talloc_steal(ac->referrals, ares->referral);
		ac->num_refs++;
		ac->referrals[ac->num_refs] = NULL;

		break;

	case LDB_REPLY_DONE:

		ret = server_sort_results(ac);
		return ldb_module_done(ac->req, ares->controls,
					ares->response, ret);
	}

	talloc_free(ares);
	return LDB_SUCCESS;
}

static int server_sort_search(struct ldb_module *module, struct ldb_request *req)
{
	struct ldb_control *control;
	struct ldb_server_sort_control **sort_ctrls;
	struct ldb_control **saved_controls;
	struct ldb_control **controls;
	struct ldb_request *down_req;
	struct sort_context *ac;
	struct ldb_context *ldb;
	int ret;

	ldb = ldb_module_get_ctx(module);

	/* check if there's a server sort control */
	control = ldb_request_get_control(req, LDB_CONTROL_SERVER_SORT_OID);
	if (control == NULL) {
		/* not found go on */
		return ldb_next_request(module, req);
	}

	ac = talloc_zero(req, struct sort_context);
	if (ac == NULL) {
		ldb_oom(ldb);
		return LDB_ERR_OPERATIONS_ERROR;
	}

	ac->module = module;
	ac->req = req;

	sort_ctrls = talloc_get_type(control->data, struct ldb_server_sort_control *);
	if (!sort_ctrls) {
		return LDB_ERR_PROTOCOL_ERROR;
	}

	/* FIXME: we do not support more than one attribute for sorting right now */
	/* FIXME: we need to check if the attribute type exist or return an error */
		
	if (sort_ctrls[1] != NULL) {
		if (control->critical) {

			/* callback immediately */
			ret = build_response(req, &controls,
					     LDB_ERR_UNWILLING_TO_PERFORM,
					     "sort control is not complete yet");
			if (ret != LDB_SUCCESS) {
				return ldb_module_done(req, NULL, NULL,
						    LDB_ERR_OPERATIONS_ERROR);
			}

			return ldb_module_done(req, controls, NULL, ret);
		} else {
			/* just pass the call down and don't do any sorting */
			return ldb_next_request(module, req);
		}
	}

	ac->attributeName = sort_ctrls[0]->attributeName;
	ac->orderingRule = sort_ctrls[0]->orderingRule;
	ac->reverse = sort_ctrls[0]->reverse;

	ret = ldb_build_search_req_ex(&down_req, ldb, ac,
					req->op.search.base,
					req->op.search.scope,
					req->op.search.tree,
					req->op.search.attrs,
					req->controls,
					ac,
					server_sort_search_callback,
					req);
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	/* save it locally and remove it from the list */
	/* we do not need to replace them later as we
	 * are keeping the original req intact */
	if (!ldb_save_controls(control, down_req, &saved_controls)) {
		return LDB_ERR_OPERATIONS_ERROR;
	}

	return ldb_next_request(module, down_req);
}

static int server_sort_init(struct ldb_module *module)
{
	struct ldb_context *ldb;
	int ret;

	ldb = ldb_module_get_ctx(module);

	ret = ldb_mod_register_control(module, LDB_CONTROL_SERVER_SORT_OID);
	if (ret != LDB_SUCCESS) {
		ldb_debug(ldb, LDB_DEBUG_WARNING,
			"server_sort:"
			"Unable to register control with rootdse!");
	}

	return ldb_next_init(module);
}

static const struct ldb_module_ops ldb_server_sort_module_ops = {
	.name		   = "server_sort",
	.search            = server_sort_search,
	.init_context	   = server_sort_init
};

int ldb_server_sort_init(const char *version)
{
	LDB_MODULE_CHECK_VERSION(version);
	return ldb_register_module(&ldb_server_sort_module_ops);
}