/*
 * (C) Copyright Fuji Xerox Co., Ltd. All rights are reserved.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * 2017/12/15	nobuo.iwata@fujixerox.co.jp
 */

#include "fxlsm_array.h"
#include "fxlsm_namespace.h"
#include "fxlsm_log.h"
#ifndef FXLSM_LIST_USER
#include <linux/slab.h>
#include <linux/bsearch.h>
#endif

static const char *array_names[FXLSM_NUM_ARRAYS] = {
	"BLACK_PATHS",
	"WHITE_PATHS",
	"BLACK_DPATHS",
	"WHITE_DPATHS",
	"BLACK_DIRS",
	"WHITE_DIRS",
	"BLACK_FILES",
	"WHITE_FILES",
	"LOST_FOUND"
};

static const unsigned char array_types[FXLSM_NUM_ARRAYS] = {
	FXLSM_TYPE_PATH,
	FXLSM_TYPE_PATH,
	FXLSM_TYPE_DPATH,
	FXLSM_TYPE_DPATH,
	FXLSM_TYPE_DIR,
	FXLSM_TYPE_DIR,
	FXLSM_TYPE_FILE,
	FXLSM_TYPE_FILE,
	0xff
};

static inline int is_lost_found(int index)
{
	return (index == FXLSM_ARRAY_LOST_FOUND);
}

static inline int array_index(
		struct fxlsm_array *a, struct fxlsm_arrays *arrays)
{
	return a - arrays->arrays;
}

static int e2array_index(struct fxlsm_entry *e)
{
	unsigned char mode = fxlsm_get_mode(e);
	unsigned char type = fxlsm_get_type(e);
	if (mode == FXLSM_MODE_WHITE) {
		if (type == FXLSM_TYPE_PATH)
			return FXLSM_ARRAY_WHITE_PATHS;
		if (type == FXLSM_TYPE_DPATH)
			return FXLSM_ARRAY_WHITE_DPATHS;
		if (type == FXLSM_TYPE_FILE)
			return FXLSM_ARRAY_WHITE_FILES;
		return FXLSM_ARRAY_LOST_FOUND;
	}
	if (type == FXLSM_TYPE_PATH)
		return FXLSM_ARRAY_BLACK_PATHS;
	if (type == FXLSM_TYPE_DPATH)
		return FXLSM_ARRAY_BLACK_DPATHS;
	if (type == FXLSM_TYPE_FILE)
		return FXLSM_ARRAY_BLACK_FILES;
	return FXLSM_ARRAY_LOST_FOUND;
}

static void array_free(struct fxlsm_array *array)
{
	if (array->entries) {
		kfree(array->entries);
		array->entries = NULL;
	}
	array->num = 0;
}

void fxlsm_arrays_free(struct fxlsm_arrays *arrays)
{
	struct fxlsm_array *a;

	if (arrays) {
		arrays_for_each(a, arrays)
			array_free(a);
		if (arrays->list) {
			clear_bit(FXLSM_LIST_BIT_ARRAYS_VALID,
				&arrays->list->flags);
			arrays->list = NULL;
		}
	}
}

struct fxlsm_array_count {
	struct fxlsm_entry *prev;
	unsigned int num;
};

static inline void array_count_init(struct fxlsm_array_count *count)
{
	count->prev = NULL;
	count->num = 0;
}

static inline void array_counts_init(struct fxlsm_array_count *counts)
{
	int i;

	for (i = 0; i < FXLSM_NUM_ARRAYS; i++)
		array_count_init(counts + i);
}

static inline int check_ascending(
		struct fxlsm_entry *prev, struct fxlsm_entry *cur)
{
	int ret = strcmp(prev->string, cur->string);

	if (ret)
		return ret;
	if (fxlsm_is_white(prev) != fxlsm_is_white(cur)) {
		WRN("entries with same path and different mode %d", cur->nr);
		return 1;
	}
	return 0;
}

static int array_count(
		const char *name, struct fxlsm_array_count *counts,
		struct fxlsm_entry *e)
{
	int index = e2array_index(e);
	struct fxlsm_array_count *count;
	int ret;

	count = counts + index;
	if (!is_lost_found(index) && count->prev) {
		ret = check_ascending(count->prev, e);
		if (ret < 0) {
			++(count->num);
		} else if (ret > 0) {
			WRN("not ascending %s %u: %s : %s",
				name, e->nr, count->prev->string, e->string);
			return -1;
		} else {
			e->flags |= FXLSM_ENTRY_FLAG_DUP;
			WRN("duplicated in %s %u, will be marged: %s",
				 name, e->nr, e->string);
		}
	} else {
		++(count->num);
	}
	count->prev = e;
	return 0;
}

static int arrays_alloc(
		const char *name, struct fxlsm_array_count *counts,
		struct fxlsm_arrays *arrays)
{
	struct fxlsm_array *a;
	struct fxlsm_array_count *count = counts;
	size_t sz;

	arrays_for_each(a, arrays) {
		sz = sizeof(void *) * count->num;
		a->entries = (struct fxlsm_entry **)kmalloc(sz, GFP_KERNEL);
		if (!a->entries) {
			ERR("kmalloc %zu arrays for %s", sz, name);
			goto err_free;
		}
		++count;
	}
	return 0;
err_free:
	fxlsm_arrays_free(arrays);
	return -1;
}

static int array_set(
		const char *name, struct fxlsm_array_count *counts,
		struct fxlsm_entry *e, struct fxlsm_arrays *arrays,
		struct fxlsm_list *list)
{
	int index = e2array_index(e);
	struct fxlsm_array_count *count = counts + index;
	struct fxlsm_array *array = arrays2array(arrays, index);
	struct fxlsm_entry *prev = NULL;

	if (index < 0)
		return 0; /* eg. namespace */
	if (e->flags & FXLSM_ENTRY_FLAG_DUP) {
		if (array->num == 0) {
			ERR("something wrong %s %u to marge entry: %s",
				name, e->nr, e->string);
			return -1;
		}
		prev = *(array->entries + (array->num - 1));
		prev->acc |= e->acc;
		list_del(&e->node);
		kfree(e);
		--(list->num);
		INFO("marged entries %s %u and %u as %u",
				name, prev->nr, e->nr, prev->acc);
	} else {
		if (array->num >= count->num) {
			ERR("something wrong %s entries for %s exceeds %u",
				name, array_names[index], count->num);
			return -1;
		}
		*(array->entries + array->num) = e;
		++(array->num);
	}
	return 0;
}

static int arrays_complete(
		const char *name, struct fxlsm_list *list,
		struct fxlsm_array_count *counts, struct fxlsm_arrays *arrays)
{
	struct fxlsm_array *a;
	struct fxlsm_array_count *count = counts;

	arrays_for_each(a, arrays) {
		if (a->num != count->num) {
			WRN("unmatch count of array %u %u for %s",
				a->num, count->num, name);
			return -1;
		}
		++count;
	}
	arrays->list = list;
	set_bit(FXLSM_LIST_BIT_ARRAYS_VALID, &list->flags);
	return 0;
}

void fxlsm_arrays_setup(struct fxlsm_list *list)
{
	struct fxlsm_arrays *arrays = (struct fxlsm_arrays *)list->arrays;
	const char *name = list->name;
	struct fxlsm_entry *pos, *n;
	struct fxlsm_array_count counts[FXLSM_NUM_ARRAYS];

	if (!list->num)
		return;
	array_counts_init(counts);
	list_for_each_entry(pos, &list->list, node) {
		if (array_count(name, counts, pos)) {
			WRN("%s is not sorted", name);
			goto err_out;
		}
	}
	if (arrays_alloc(name, counts, arrays))
		goto err_out;
	list_for_each_entry_safe(pos, n, &list->list, node) {
		if (array_set(name, counts, pos, arrays, list)) {
			WRN("failed to setup arrray for %s", name);
			goto err_out;
		}
	}
	if (arrays_complete(name, list, counts, arrays))
		goto err_out;
	return;
err_out:
	fxlsm_arrays_free(arrays);
	return;
}

static void array_print(struct fxlsm_arrays *arrays, struct fxlsm_array *a)
{
	unsigned int i;

	if (!a->num)
		return;
	PR("=== array %s:%s (%u) ===",
		arrays->list->name,
		array_names[array_index(a, arrays)], a->num);
	for (i = 0; i < a->num; i++)
		fxlsm_entry_print("", *(a->entries + i));
}

void fxlsm_arrays_print(struct fxlsm_arrays *arrays)
{
	struct fxlsm_array *a;

	arrays_for_each(a, arrays) {
		array_print(arrays, a);
	}
}

static int cmp_full(struct fxlsm_string *key, struct fxlsm_entry **ep)
{
	struct fxlsm_entry *e = *ep;
	unsigned int e_len = fxlsm_get_len(e);
	unsigned int len;
	int ret;

	if (key->len == e->len) {
		return memcmp(key->s, e->string, e_len);
	}
	len = (key->len < e_len) ? key->len : e_len;
	ret = memcmp(key->s, e->string, len);
	if (ret)
		return ret;
	if (key->len < e->len)
		return -1;
	return 1;
}

static int cmp_part(struct fxlsm_string *key, struct fxlsm_entry **ep)
{
	struct fxlsm_entry *e = *ep;
	int ret;

	if (key->len >= e->len) {
		return memcmp(key->s, e->string, e->len);
	}
	ret = memcmp(key->s, e->string, key->len);
	if (ret)
		return ret;
	return -1;
}

static struct fxlsm_entry *array_find(
		struct fxlsm_arrays *arrays, struct fxlsm_array *a,
		struct fxlsm_find_params *par)
{
	struct fxlsm_string *key;
	struct fxlsm_entry **ep;
	unsigned char type = array_types[array_index(a, arrays)];
	int (*cmp)(const void *, const void *);

	if (type == FXLSM_TYPE_PATH) {
		key = &par->path;
		cmp = (int (*)(const void *, const void *))cmp_full;
	} else if (type == FXLSM_TYPE_DPATH) {
		key = &par->path;
		cmp = (int (*)(const void *, const void *))cmp_full;
	} else if (type == FXLSM_TYPE_DIR) {
		key = &par->path;
		cmp = (int (*)(const void *, const void *))cmp_part;
	} else if (type == FXLSM_TYPE_FILE) {
		key = &par->file;
		cmp = (int (*)(const void *, const void *))cmp_full;
	} else {	
		return NULL;
	}
	ep = (struct fxlsm_entry **)
		bsearch(key, a->entries, a->num, sizeof(void *), cmp);
	if (ep)
		return *ep;
	return NULL;
}

struct fxlsm_entry *fxlsm_arrays_find(
		struct fxlsm_arrays *arrays,
		unsigned char acc, unsigned char *__acc,
		struct fxlsm_find_params *par)
{
	struct fxlsm_array *a;
	struct fxlsm_entry *e;

	if (par->ns)
		return fxlsm_ns_array_find(arrays, acc, __acc, par);

	arrays_for_each(a, arrays) {
		e = array_find(arrays, a, par);
		if (e && (e->acc & acc)) {
			*__acc = e->acc & acc;
			return e;
		}
	}
	return NULL;
}
