#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <regex.h>

#include "cctype.h"
#include "rpmatch.h"
#include "smartcolsP.h"

struct filter_param {
	struct filter_node node;
	int type;
	enum filter_holder holder;

	union {
		char *str;
		unsigned long long num;
		long double fnum;
		bool boolean;
	} val;

	struct list_head pr_params;
	struct libscols_column *col;
	char *holder_name;
	regex_t *re;

	bool fetched,	/* holder requested */
	     empty;
};

static int cast_param(int type, struct filter_param *n);

static inline const char *datatype2str(int type)
{
	static const char *const types[] = {
		[SCOLS_DATA_NONE] = "none",
		[SCOLS_DATA_STRING] = "string",
		[SCOLS_DATA_U64] = "u64",
		[SCOLS_DATA_FLOAT] = "float",
		[SCOLS_DATA_BOOLEAN] = "boolean"
	};
	return types[type];
}

static char *rem_quotation(const char *p, int c)
{
	size_t len = strlen(p);

	if (*(p + (len - 1)) == c)
		len -= 2;
	return strndup(p + 1, len);
}

static int param_set_data(struct filter_param *n, int type, const void *data)
{
	const char *p;

	/*DBG(FPARAM, ul_debugobj(n, " set %s data", datatype2str(type)));*/

	switch (type) {
	case SCOLS_DATA_STRING:
		p = data;
		if (p && (*p == '"' || *p == '\''))
			n->val.str = rem_quotation(p, *p);
		else if (data)
			n->val.str = strdup((char *) data);
		if (data && !n->val.str)
			return -ENOMEM;
		if (data) {
			rtrim_whitespace((unsigned char *) n->val.str);
			ltrim_whitespace((unsigned char *) n->val.str);
		}
		break;
	case SCOLS_DATA_U64:
		n->val.num = data ? *((unsigned long long *) data) : 0;
		break;
	case SCOLS_DATA_FLOAT:
		n->val.fnum = data ? *((long double *) data) : 0;
		break;
	case SCOLS_DATA_BOOLEAN:
		n->val.boolean = data ? (*((bool *) data) == 0 ? 0 : 1) : 0;
		break;
	default:
		return 0;
	}

	n->type = type;
	n->empty = data == NULL;
	return 0;
}

struct filter_node *filter_new_param(
		struct libscols_filter *fltr,
		int type,
		enum filter_holder holder,
		void *data)
{
	struct filter_param *n = (struct filter_param *) __filter_new_node(
					F_NODE_PARAM,
					sizeof(struct filter_param));
	if (!n)
		return NULL;

	n->type = type;
	n->holder = holder;
	INIT_LIST_HEAD(&n->pr_params);

	if (param_set_data(n, type, data) != 0) {
		filter_free_param(n);
		return NULL;
	}

	if (holder == F_HOLDER_COLUMN) {
		n->holder_name = strdup((char *) data);
		DBG(FLTR, ul_debugobj(fltr, "new %s holder", n->holder_name));
	}

	if (fltr)
		list_add_tail(&n->pr_params, &fltr->params);

	return (struct filter_node *) n;
}

int filter_compile_param(struct libscols_filter *fltr, struct filter_param *n)
{
	int rc;

	if (n->re)
		return 0;
	if (!n->val.str)
		return -EINVAL;

	n->re = calloc(1, sizeof(regex_t));
	if (!n->re)
		return -ENOMEM;

	rc = regcomp(n->re, n->val.str, REG_NOSUB | REG_EXTENDED);
	if (rc) {
		size_t size = regerror(rc, n->re, NULL, 0);

		fltr->errmsg = malloc(size + 1);
		if (!fltr->errmsg)
			return -ENOMEM;
		regerror(rc, n->re, fltr->errmsg, size);
		return -EINVAL;
	}
	return 0;
}

static struct filter_param *copy_param(struct filter_param *n)
{
	void *data = NULL;

	switch (n->type) {
	case SCOLS_DATA_STRING:
		data = n->val.str;
		break;
	case SCOLS_DATA_U64:
		data = &n->val.num;
		break;
	case SCOLS_DATA_FLOAT:
		data = &n->val.fnum;
		break;
	case SCOLS_DATA_BOOLEAN:
		data = &n->val.boolean;
		break;
	}

	DBG(FPARAM, ul_debugobj(n, "copying"));
	return (struct filter_param *) filter_new_param(NULL, n->type, F_HOLDER_NONE, data);
}

static void param_reset_data(struct filter_param *n)
{
	if (n->type == SCOLS_DATA_STRING)
		free(n->val.str);

	memset(&n->val, 0, sizeof(n->val));
	n->fetched = 0;
	n->empty = 1;

	if (n->re) {
		regfree(n->re);
		free(n->re);
		n->re = NULL;
	}
}

void filter_free_param(struct filter_param *n)
{
	param_reset_data(n);

	free(n->holder_name);
	list_del_init(&n->pr_params);
	scols_unref_column(n->col);
	free(n);
}

int filter_param_get_datatype(struct filter_param *n)
{
	return n ? n->type : SCOLS_DATA_NONE;
}

int is_filter_holder_node(struct filter_node *n)
{
	return n && filter_node_get_type(n) == F_NODE_PARAM
                && ((struct filter_param *)(n))->holder;
}

void filter_dump_param(struct ul_jsonwrt *json, struct filter_param *n)
{
	ul_jsonwrt_object_open(json, "param");

	if (n->empty) {
		ul_jsonwrt_value_boolean(json, "empty", true);
		ul_jsonwrt_value_s(json, "type", datatype2str(n->type));
	} else {
		switch (n->type) {
		case SCOLS_DATA_STRING:
			ul_jsonwrt_value_s(json, "string", n->val.str);
			break;
		case SCOLS_DATA_U64:
			ul_jsonwrt_value_u64(json, "number", n->val.num);
			break;
		case SCOLS_DATA_FLOAT:
			ul_jsonwrt_value_double(json, "float", n->val.fnum);
			break;
		case SCOLS_DATA_BOOLEAN:
			ul_jsonwrt_value_boolean(json, "bool", n->val.boolean);
			break;
		default:
			break;
		}
	}

	if (n->holder == F_HOLDER_COLUMN)
		ul_jsonwrt_value_s(json, "column", n->holder_name);

	ul_jsonwrt_object_close(json);
}

int filter_param_reset_holder(struct filter_param *n)
{
	if (!n->holder || !n->col)
		return 0;

	param_reset_data(n);

	if (n->type != SCOLS_DATA_NONE)
		return 0; /* already set */

	if (scols_column_get_data_type(n->col))
		/* use by application defined type */
		n->type = scols_column_get_data_type(n->col);
	else {
		/* use by JSON defined type, default to string if not specified */
		switch (n->col->json_type) {
		case SCOLS_JSON_NUMBER:
			n->type = SCOLS_DATA_U64;
			break;
		case SCOLS_JSON_BOOLEAN:
			n->type = SCOLS_DATA_BOOLEAN;
			break;
		case SCOLS_JSON_FLOAT:
			n->type = SCOLS_DATA_FLOAT;
			break;
		case SCOLS_JSON_STRING:
		default:
			n->type = SCOLS_DATA_STRING;
			break;
		}
	}

	DBG(FPARAM, ul_debugobj(n, "holder %s type: %s", n->holder_name, datatype2str(n->type)));
	return 0;
}

static int fetch_holder_data(struct libscols_filter *fltr __attribute__((__unused__)),
			struct filter_param *n, struct libscols_line *ln)
{
	const char *data = NULL;
	struct libscols_column *cl = n->col;
	int type = n->type;
	int rc = 0;

	if (n->fetched || n->holder != F_HOLDER_COLUMN)
		return 0;
	if (!cl) {
		DBG(FPARAM, ul_debugobj(n, "no column for %s holder", n->holder_name));
		return -EINVAL;
	}
	DBG(FPARAM, ul_debugobj(n, "fetching %s data", n->holder_name));

	if (fltr->filler_cb && !scols_line_is_filled(ln, cl->seqnum)) {
		DBG(FPARAM, ul_debugobj(n, "  by callback"));
		rc = fltr->filler_cb(fltr, ln, cl->seqnum, fltr->filler_data);
		if (rc)
			return rc;
	}

	n->fetched = 1;

	if (scols_column_has_data_func(cl)) {
		struct libscols_cell *ce = scols_line_get_column_cell(ln, cl);

		DBG(FPARAM, ul_debugobj(n, " using datafunc()"));
		if (ce)
			data = cl->datafunc(n->col, ce, cl->datafunc_data);
		if (data)
			rc = param_set_data(n, scols_column_get_data_type(cl), data);
	} else {
		DBG(FPARAM, ul_debugobj(n, " using as string"));
		data = scols_line_get_column_data(ln, n->col);
		rc = param_set_data(n, SCOLS_DATA_STRING, data);
	}

	/* cast to the wanted type */
	if (rc == 0 && type != SCOLS_DATA_NONE)
		rc = cast_param(type, n);
	return rc;
}

int filter_eval_param(struct libscols_filter *fltr,
		struct libscols_line *ln,
		struct filter_param *n,
		int *status)
{
	int rc = 0;

	DBG(FLTR, ul_debugobj(fltr, "eval param"));

	rc = fetch_holder_data(fltr, n, ln);
	if (n->empty || rc) {
		*status = 0;
		goto done;
	}

	switch (n->type) {
	case SCOLS_DATA_STRING:
		*status = n->val.str != NULL && *n->val.str != '\0';
		break;
	case SCOLS_DATA_U64:
		*status = n->val.num != 0;
		break;
	case SCOLS_DATA_FLOAT:
		*status = n->val.fnum != 0.0;
		break;
	case SCOLS_DATA_BOOLEAN:
		*status = n->val.boolean != false;
		break;
	default:
		rc = -EINVAL;
		break;
	}
done:
	if (rc)
		DBG(FLTR, ul_debugobj(fltr, "failed eval param [rc=%d]", rc));
	return rc;
}

int filter_count_param(struct libscols_filter *fltr,
		struct libscols_line *ln,
		struct libscols_counter *ct)
{
	unsigned long long num = 0;

	if (ct->func == SCOLS_COUNTER_COUNT) {
		ct->result++;
		return 0;
	}

	if (ct->param) {
		int rc;

		ct->param->type = SCOLS_DATA_U64;
		rc = fetch_holder_data(fltr, ct->param, ln);
		if (rc)
			return rc;

		if (ct->param->empty)
			return -EINVAL;

		num = ct->param->val.num;
	}

	switch (ct->func) {
	case SCOLS_COUNTER_MAX:
		if (!ct->has_result)
			ct->result = num;
		else if (num > ct->result)
			ct->result = num;
		break;
	case SCOLS_COUNTER_MIN:
		if (!ct->has_result)
			ct->result = num;
		else if (num < ct->result)
			ct->result = num;
		break;
	case SCOLS_COUNTER_SUM:
		ct->result += num;
		break;
	default:
		return -EINVAL;
	}

	ct->has_result = 1;
	DBG(FLTR, ul_debugobj(fltr, "counted '%s' [result: %llu]", ct->name, ct->result));
	return 0;
}

static int xstrcmp(char *a, char *b)
{
	if (!a && !b)
		return 0;
	if (!a && b)
		return -1;
	if (a && !b)
		return 1;
	return strcmp(a, b);
}

static int string_opers(enum filter_etype oper, struct filter_param *l,
			struct filter_param *r, int *status)
{
	switch (oper) {
	case F_EXPR_EQ:
		*status = xstrcmp(l->val.str, r->val.str) == 0;
		break;
	case F_EXPR_NE:
		*status = xstrcmp(l->val.str, r->val.str) != 0;
		break;
	case F_EXPR_LE:
		*status = xstrcmp(l->val.str, r->val.str) <= 0;
		break;
	case F_EXPR_LT:
		*status = xstrcmp(l->val.str, r->val.str) < 0;
		break;
	case F_EXPR_GE:
		*status = xstrcmp(l->val.str, r->val.str) >= 0;
		break;
	case F_EXPR_GT:
		*status = xstrcmp(l->val.str, r->val.str) > 0;
		break;
	case F_EXPR_REG:
		if (!r->re)
			return -EINVAL;
		*status = regexec(r->re, l->val.str ? : "", 0, NULL, 0) == 0;
		break;
	case F_EXPR_NREG:
		if (!r->re)
			return -EINVAL;
		*status = regexec(r->re, l->val.str ? : "", 0, NULL, 0) != 0;
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static int u64_opers(enum filter_etype oper, struct filter_param *l,
			     struct filter_param *r, int *status)
{
	if (l->empty || r->empty) {
		*status = 0;
		return 0;
	}

	switch (oper) {
	case F_EXPR_EQ:
		*status = l->val.num == r->val.num;
		break;
	case F_EXPR_NE:
		*status = l->val.num != r->val.num;
		break;
	case F_EXPR_LE:
		*status = l->val.num <= r->val.num;
		break;
	case F_EXPR_LT:
		*status = l->val.num < r->val.num;
		break;
	case F_EXPR_GE:
		*status = l->val.num >= r->val.num;
		break;
	case F_EXPR_GT:
		*status = l->val.num > r->val.num;
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static int float_opers(enum filter_etype oper, struct filter_param *l,
			     struct filter_param *r, int *status)
{
	if (l->empty || r->empty) {
		*status = 0;
		return 0;
	}

	switch (oper) {
	case F_EXPR_EQ:
		*status = l->val.fnum == r->val.fnum;
		break;
	case F_EXPR_NE:
		*status = l->val.fnum != r->val.fnum;
		break;
	case F_EXPR_LE:
		*status = l->val.fnum <= r->val.fnum;
		break;
	case F_EXPR_LT:
		*status = l->val.fnum < r->val.fnum;
		break;
	case F_EXPR_GE:
		*status = l->val.fnum >= r->val.fnum;
		break;
	case F_EXPR_GT:
		*status = l->val.fnum > r->val.fnum;
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static int bool_opers(enum filter_etype oper, struct filter_param *l,
			     struct filter_param *r, int *status)
{
	if (l->empty || r->empty) {
		*status = 0;
		return 0;
	}

	switch (oper) {
	case F_EXPR_EQ:
		*status = l->val.boolean == r->val.boolean;
		break;
	case F_EXPR_NE:
		*status = l->val.boolean != r->val.boolean;
		break;
	case F_EXPR_LE:
		*status = l->val.boolean <= r->val.boolean;
		break;
	case F_EXPR_LT:
		*status = l->val.boolean < r->val.boolean;
		break;
	case F_EXPR_GE:
		*status = l->val.boolean >= r->val.boolean;
		break;
	case F_EXPR_GT:
		*status = l->val.boolean > r->val.boolean;
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

/* call filter_cast_param() to be sure that param data are ready (fetched from
 * holder, etc.) */
int filter_compare_params(struct libscols_filter *fltr __attribute__((__unused__)),
			  enum filter_etype oper,
			  struct filter_param *l,
			  struct filter_param *r,
			  int *status)
{
	int rc;

	if (!l || !r || l->type != r->type)
		return -EINVAL;

	*status = 0;

	switch (l->type) {
	case SCOLS_DATA_STRING:
		rc = string_opers(oper, l, r, status);
		break;
	case SCOLS_DATA_U64:
		rc = u64_opers(oper, l, r, status);
		break;
	case SCOLS_DATA_FLOAT:
		rc = float_opers(oper, l, r, status);
		break;
	case SCOLS_DATA_BOOLEAN:
		rc = bool_opers(oper, l, r, status);
		break;
	default:
		rc = -EINVAL;
		break;
	}

	return rc;
}

static int string_cast(int type, struct filter_param *n)
{
	char *str = n->val.str;

	if (type == SCOLS_DATA_STRING)
		return 0;

	n->val.str = NULL;

	switch (type) {
	case SCOLS_DATA_U64:
	{
		uint64_t num = 0;
		if (str) {
			int rc = ul_strtou64(str, &num, 10);
			if (rc)
				return rc;
		}
		n->val.num = num;
		break;
	}
	case SCOLS_DATA_FLOAT:
	{
		long double num = 0;
		if (str) {
			int rc = ul_strtold(str, &num);
			if (rc)
				return rc;
		}
		n->val.fnum = num;
		break;
	}
	case SCOLS_DATA_BOOLEAN:
	{
		bool x = str && *str
			     && (strcmp(str, "1") == 0
				 || c_strcasecmp(str, "true") == 0
				 || rpmatch(str) == RPMATCH_YES);
		n->val.boolean = x;
		break;
	}
	default:
		return -EINVAL;
	}

	free(str);
	return 0;
}

static int u64_cast(int type, struct filter_param *n)
{
	unsigned long long num = n->val.num;

	switch (type) {
	case SCOLS_DATA_STRING:
		n->val.str = NULL;
		if (asprintf(&n->val.str, "%llu", num) <= 0)
			return -ENOMEM;
		break;
	case SCOLS_DATA_U64:
		break;
	case SCOLS_DATA_FLOAT:
		n->val.fnum = num;
		break;
	case SCOLS_DATA_BOOLEAN:
		n->val.boolean = num > 0 ? true : false;
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static int float_cast(int type, struct filter_param *n)
{
	long double fnum = n->val.fnum;

	switch (type) {
	case SCOLS_DATA_STRING:
		n->val.str = NULL;
		if (asprintf(&n->val.str, "%Lg", fnum) <= 0)
			return -ENOMEM;
		break;
	case SCOLS_DATA_U64:
		n->val.num = fnum;
		break;
	case SCOLS_DATA_FLOAT:
		break;
	case SCOLS_DATA_BOOLEAN:
		n->val.boolean = fnum > 0.0 ? true : false;
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static int bool_cast(int type, struct filter_param *n)
{
	bool x = n->val.boolean;

	switch (type) {
	case SCOLS_DATA_STRING:
		n->val.str = NULL;
		if (asprintf(&n->val.str, "%s", x ? "true" : "false") <= 0)
			return -ENOMEM;
		break;
	case SCOLS_DATA_U64:
		n->val.num = x ? 1 : 0;
		break;
	case SCOLS_DATA_FLOAT:
		n->val.fnum = x ? 1.0 : 0.0;
		break;
	case SCOLS_DATA_BOOLEAN:
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static int cast_param(int type, struct filter_param *n)
{
	int rc;
	int orgtype = n->type;

	if (type == orgtype)
		return 0;

	if (orgtype == SCOLS_DATA_STRING)
		DBG(FPARAM, ul_debugobj(n, " casting \"%s\" to %s", n->val.str, datatype2str(type)));
	else
		DBG(FPARAM, ul_debugobj(n, " casting %s to %s", datatype2str(orgtype), datatype2str(type)));

	switch (orgtype) {
	case SCOLS_DATA_STRING:
		rc = string_cast(type, n);
		break;
	case SCOLS_DATA_U64:
		rc = u64_cast(type, n);
		break;
	case SCOLS_DATA_FLOAT:
		rc = float_cast(type, n);
		break;
	case SCOLS_DATA_BOOLEAN:
		rc = bool_cast(type, n);
		break;
	default:
		rc = -EINVAL;
		break;
	}

	if (rc == 0)
		n->type = type;

	if (rc)
		DBG(FPARAM, ul_debugobj(n, "cast done [rc=%d]", rc));
	return rc;
}

int filter_cast_param(struct libscols_filter *fltr,
		      struct libscols_line *ln,
		      int type,
		      struct filter_param *n,
		      struct filter_param **result)
{
	int rc;
	int orgtype = n->type;

	DBG(FPARAM, ul_debugobj(n, "casting param to %s", datatype2str(type)));
	rc = fetch_holder_data(fltr, n, ln);
	if (rc)
		return rc;

	if (type == orgtype) {
		filter_ref_node((struct filter_node *) n);	/* caller wants to call filter_unref_node() for the result */
		*result = n;
		return 0;
	}

	*result = copy_param(n);
	if (!*result)
		return -ENOMEM;
	rc = cast_param(type, *result);

	DBG(FPARAM, ul_debugobj(n, "cast done [rc=%d]", rc));
	return rc;
}

int filter_next_param(struct libscols_filter *fltr,
		      struct libscols_iter *itr, struct filter_param **prm)
{
	int rc = 1;

	if (!fltr || !itr || !prm)
		return -EINVAL;
	*prm = NULL;

	if (!itr->head)
		SCOLS_ITER_INIT(itr, &fltr->params);
	if (itr->p != itr->head) {
		SCOLS_ITER_ITERATE(itr, *prm, struct filter_param, pr_params);
		rc = 0;
	}

	return rc;
}

/**
 * scols_filter_assign_column:
 * @fltr: pointer to filter
 * @itr: iterator
 * @name: holder name
 * @col: column
 *
 * Assign @col to filter parameter. The parameter is addressed by @itr or by
 * @name. See scols_filter_next_holder().
 *
 * Returns: 0, a negative value in case of an error.
 *
 * Since: 2.40
 */
int scols_filter_assign_column(struct libscols_filter *fltr,
			struct libscols_iter *itr,
			const char *name, struct libscols_column *col)
{
	struct filter_param *n = NULL;

	if (itr && itr->p) {
		struct list_head *p = IS_ITER_FORWARD(itr) ?
						itr->p->prev : itr->p->next;
		n = list_entry(p, struct filter_param, pr_params);
	} else if (name) {
		struct libscols_iter xitr;
		struct filter_param *x = NULL;

		scols_reset_iter(&xitr, SCOLS_ITER_FORWARD);
		while (filter_next_param(fltr, &xitr, &x) == 0) {
			if (x->col
			    || x->holder != F_HOLDER_COLUMN
			    || strcmp(name, x->holder_name) != 0)
				continue;
			n = x;
			break;
		}
	}

	if (n) {
		if (n->col)
			scols_unref_column(n->col);

		DBG(FPARAM, ul_debugobj(n, "assign %s to column %s", name,
					scols_column_get_name(col)));
		n->col = col;
		scols_ref_column(col);
	}

	return n ? 0 : -EINVAL;
}

/**
 * scols_filter_next_holder:
 * @fltr: filter instance
 * @itr: a pointer to a struct libscols_iter instance
 * @name: returns the next column name
 * @type: 0 (not implemented yet)
 *
 * Finds the next holder used in the expression and and returns a name via
 * @name. The currently supported holder type is only column name.
 *
 * Returns: 0, a negative value in case of an error, and 1 at the end.
 *
 * Since: 2.40
 */
int scols_filter_next_holder(struct libscols_filter *fltr,
			struct libscols_iter *itr,
			const char **name,
			int type)
{
	struct filter_param *prm = NULL;
	int rc = 0;

	*name = NULL;
	if (!type)
		type = F_HOLDER_COLUMN;	/* default */

	do {
		rc = filter_next_param(fltr, itr, &prm);
		if (rc == 0 && (int) prm->holder == type) {
			*name = prm->holder_name;
		}
	} while (rc == 0 && !*name);

	return rc;
}

/**
 * scols_filter_has_holder:
 * @fltr: filter instance
 * @name: wanted holder
 *
 * Returns: 0 or 1
 *
 * Since: 2.42
 */
int scols_filter_has_holder(struct libscols_filter *fltr, const char *name)
{
	struct libscols_iter itr;
	const char *n = NULL;

	if (!fltr || !name)
		return 0;

	scols_reset_iter(&itr, SCOLS_ITER_FORWARD);

	while (scols_filter_next_holder(fltr, &itr, &n, 0) == 0) {
		if (strcmp(n, name) == 0)
			return 1;
	}

	return 0;
}
