/*
 * MARS Long Distance Replication Software
 *
 * This file is part of MARS project: http://schoebel.github.io/mars/
 *
 * Copyright (C) 2010-2014 Thomas Schoebel-Theuer
 * Copyright (C) 2011-2014 1&1 Internet AG
 *
 * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>

//#define BRICK_DEBUGGING

#define _STRATEGY

#include "brick.h"
#include "brick_mem.h"

int _brick_msleep(int msecs, bool shorten)
{
	unsigned long timeout;
	flush_signals(current);
	if (msecs <= 0) {
		cond_resched();
		return 0;
	}
	timeout = msecs_to_jiffies(msecs) + 1;

	timeout = schedule_timeout_interruptible(timeout);

	if (!shorten) {
		while ((long)timeout > 0) {
			timeout = schedule_timeout_uninterruptible(timeout);
		}
	}

	return jiffies_to_msecs(timeout);
}
EXPORT_SYMBOL_GPL(_brick_msleep);

//////////////////////////////////////////////////////////////

// number management

static char *nr_table = NULL;
int nr_max = 256;
EXPORT_SYMBOL_GPL(nr_max);

int get_nr(void)
{
	char *new;
	int nr;

	if (unlikely(!nr_table)) {
		nr_table = brick_zmem_alloc(nr_max);
		if (!nr_table) {
			return 0;
		}
	}

	for (;;) {
		for (nr = 1; nr < nr_max; nr++) {
			if (!nr_table[nr]) {
				nr_table[nr] = 1;
				return nr;
			}
		}
		new = brick_zmem_alloc(nr_max << 1);
		if (!new)
			return 0;
		memcpy(new, nr_table, nr_max);
		brick_mem_free(nr_table);
		nr_table = new;
		nr_max <<= 1;
	}
}
EXPORT_SYMBOL_GPL(get_nr);

void put_nr(int nr)
{
	if (likely(nr_table && nr > 0 && nr < nr_max)) {
		nr_table[nr] = 0;
	}
}
EXPORT_SYMBOL_GPL(put_nr);

//////////////////////////////////////////////////////////////

// object stuff

//////////////////////////////////////////////////////////////

// brick stuff

static int nr_brick_types = 0;
static const struct generic_brick_type *brick_types[MAX_BRICK_TYPES] = {};

int generic_register_brick_type(const struct generic_brick_type *new_type)
{
	int i;
	int found = -1;
	BRICK_DBG("generic_register_brick_type() name=%s\n", new_type->type_name);
	for (i = 0; i < nr_brick_types; i++) {
		if (!brick_types[i]) {
			found = i;
			continue;
		}
		if (!strcmp(brick_types[i]->type_name, new_type->type_name)) {
			BRICK_DBG("bricktype %s is already registered.\n", new_type->type_name);
			return 0;
		}
	}
	if (found < 0) {
		if (nr_brick_types >= MAX_BRICK_TYPES) {
			BRICK_ERR("sorry, cannot register bricktype %s.\n", new_type->type_name);
			return -ENOMEM;
		}
		found = nr_brick_types++;
	}
	brick_types[found] = new_type;
	BRICK_DBG("generic_register_brick_type() done.\n");
	return 0;
}
EXPORT_SYMBOL_GPL(generic_register_brick_type);

int generic_unregister_brick_type(const struct generic_brick_type *old_type)
{
	BRICK_DBG("generic_unregister_brick_type()\n");
	return -1; // NYI
}
EXPORT_SYMBOL_GPL(generic_unregister_brick_type);

int generic_brick_init_full(
	void *data, 
	int size, 
	const struct generic_brick_type *brick_type,
	const struct generic_input_type **input_types,
	const struct generic_output_type **output_types,
	const char **names)
{
	struct generic_brick *brick = data;
	int status;
	int i;

	BRICK_DBG("brick_type = %s\n", brick_type->type_name);
	if (unlikely(!data)) {
		BRICK_ERR("invalid memory\n");
		return -EINVAL;
	}

	// call the generic constructors

	status = generic_brick_init(brick_type, brick, names ? *names++ : NULL);
	if (status)
		return status;
	data += brick_type->brick_size;
	size -= brick_type->brick_size;
	if (size < 0) {
		BRICK_ERR("Not enough MEMORY\n");
		return -ENOMEM;
	}
	if (!input_types) {
		BRICK_DBG("generic_brick_init_full: switch to default input_types\n");
		input_types = brick_type->default_input_types;
		names = brick_type->default_input_names;
		if (unlikely(!input_types)) {
			BRICK_ERR("no input types specified\n");
			return -EINVAL;
		}
	}
	BRICK_DBG("generic_brick_init_full: input_types\n");
	brick->inputs = data;
	data += sizeof(void*) * brick_type->max_inputs;
	size -= sizeof(void*) * brick_type->max_inputs;
	if (size < 0) {
		return -ENOMEM;
	}
	for (i = 0; i < brick_type->max_inputs; i++) {
		struct generic_input *input = data;
		const struct generic_input_type *type = *input_types++;
		if (!type || type->input_size <= 0) {
			return -EINVAL;
		}
		BRICK_DBG("generic_brick_init_full: calling generic_input_init()\n");
		status = generic_input_init(brick, i, type, input, (names && *names) ? *names++ : type->type_name);
		if (status < 0)
			return status;
		data += type->input_size;
		size -= type->input_size;
		if (size < 0)
			return -ENOMEM;
	}
	if (!output_types) {
		BRICK_DBG("generic_brick_init_full: switch to default output_types\n");
		output_types = brick_type->default_output_types;
		names = brick_type->default_output_names;
		if (unlikely(!output_types)) {
			BRICK_ERR("no output types specified\n");
			return -EINVAL;
		}
	}
	BRICK_DBG("generic_brick_init_full: output_types\n");
	brick->outputs = data;
	data += sizeof(void*) * brick_type->max_outputs;
	size -= sizeof(void*) * brick_type->max_outputs;
	if (size < 0)
		return -ENOMEM;
	for (i = 0; i < brick_type->max_outputs; i++) {
		struct generic_output *output = data;
		const struct generic_output_type *type = *output_types++;
		if (!type || type->output_size <= 0) {
			return -EINVAL;
		}
		BRICK_DBG("generic_brick_init_full: calling generic_output_init()\n");
		generic_output_init(brick, i, type, output, (names && *names) ? *names++ : type->type_name);
		if (status < 0)
			return status;
		data += type->output_size;
		size -= type->output_size;
		if (size < 0)
			return -ENOMEM;
	}

	// call the specific constructors
	BRICK_DBG("generic_brick_init_full: call specific contructors.\n");
	if (brick_type->brick_construct) {
		BRICK_DBG("generic_brick_init_full: calling brick_construct()\n");
		status = brick_type->brick_construct(brick);
		if (status < 0)
			return status;
	}
	for (i = 0; i < brick_type->max_inputs; i++) {
		struct generic_input *input = brick->inputs[i];
		if (!input)
			continue;
		if (!input->type) {
			BRICK_ERR("input has no associated type!\n");
			continue;
		}
		if (input->type->input_construct) {
			BRICK_DBG("generic_brick_init_full: calling input_construct()\n");
			status = input->type->input_construct(input);
			if (status < 0)
				return status;
		}
	}
	for (i = 0; i < brick_type->max_outputs; i++) {
		struct generic_output *output = brick->outputs[i];
		if (!output)
			continue;
		if (!output->type) {
			BRICK_ERR("output has no associated type!\n");
			continue;
		}
		if (output->type->output_construct) {
			BRICK_DBG("generic_brick_init_full: calling output_construct()\n");
			status = output->type->output_construct(output);
			if (status < 0)
				return status;
		}
	}
	return 0;
}
EXPORT_SYMBOL_GPL(generic_brick_init_full);

int generic_brick_exit_full(struct generic_brick *brick)
{
	int i;
	int status;
	// first, check all outputs
	for (i = 0; i < brick->type->max_outputs; i++) {
		struct generic_output *output = brick->outputs[i];
		if (!output)
			continue;
		if (!output->type) {
			BRICK_ERR("output has no associated type!\n");
			continue;
		}
		if (output->nr_connected) {
			BRICK_ERR("output is connected!\n");
			return -EPERM;
		}
	}
        // ok, test succeeded. start destruction...
	for (i = 0; i < brick->type->max_outputs; i++) {
		struct generic_output *output = brick->outputs[i];
		if (!output)
			continue;
		if (!output->type) {
			BRICK_ERR("output has no associated type!\n");
			continue;
		}
		if (output->type->output_destruct) {
			BRICK_DBG("generic_brick_exit_full: calling output_destruct()\n");
			status = output->type->output_destruct(output);
			if (status < 0)
				return status;
			_generic_output_exit(output);
			brick->outputs[i] = NULL; // others may remain leftover
		}
	}
	for (i = 0; i < brick->type->max_inputs; i++) {
		struct generic_input *input = brick->inputs[i];
		if (!input)
			continue;
		if (!input->type) {
			BRICK_ERR("input has no associated type!\n");
			continue;
		}
		if (input->type->input_destruct) {
			status = generic_disconnect(input);
			if (status < 0)
				return status;
			BRICK_DBG("generic_brick_exit_full: calling input_destruct()\n");
			status = input->type->input_destruct(input);
			if (status < 0)
				return status;
			brick->inputs[i] = NULL; // others may remain leftover
			generic_input_exit(input);
		}
	}
	if (brick->type->brick_destruct) {
		BRICK_DBG("generic_brick_exit_full: calling brick_destruct()\n");
		status = brick->type->brick_destruct(brick);
		if (status < 0)
			return status;
	}
	generic_brick_exit(brick);
	return 0;
}
EXPORT_SYMBOL_GPL(generic_brick_exit_full);

////////////////////////////////////////////////////////////////////////

// default implementations

struct generic_object *generic_alloc(struct generic_brick *brick, struct generic_object_layout *object_layout, const struct generic_object_type *object_type)
{
	struct generic_object *object;
	void *data;
	int object_size;
	int aspect_nr_max;
	int total_size;
	int hint_size;

	CHECK_PTR_NULL(object_type, err);
	CHECK_PTR(object_layout, err);

	object_size = object_type->default_size;
	aspect_nr_max = nr_max;
	total_size = object_size + aspect_nr_max * sizeof(void*);
	hint_size = object_layout->size_hint;
	if (likely(total_size <= hint_size)) {
		total_size = hint_size;
	} else { // usually happens only at the first time
		object_layout->size_hint = total_size;
	}

	data = brick_zmem_alloc(total_size);
	if (!data)
		goto err;

	atomic_inc(&object_layout->alloc_count);
	atomic_inc(&object_layout->total_alloc_count);

	object = data;
	object->object_type = object_type;
	object->object_layout = object_layout;
	object->aspects = data + object_size;
	object->aspect_nr_max = aspect_nr_max;
	object->free_offset = object_size + aspect_nr_max * sizeof(void*);
	object->max_offset = total_size;

	if (object_type->init_fn) {
		int status = object_type->init_fn(object);
		if (status < 0) {
			goto err_free;
		}
	}

	return object;

err_free:
	brick_mem_free(data);
err:
	return NULL;
}
EXPORT_SYMBOL_GPL(generic_alloc);

void generic_free(struct generic_object *object)
{
	const struct generic_object_type *object_type;
	struct generic_object_layout *object_layout;
	int i;

	CHECK_PTR(object, done);
	object_type = object->object_type;
	CHECK_PTR_NULL(object_type, done);
	object_layout = object->object_layout;
	CHECK_PTR(object_layout, done);
	atomic_dec(&object_layout->alloc_count);
	for (i = 0; i < object->aspect_nr_max; i++) {
		const struct generic_aspect_type *aspect_type;
		struct generic_aspect *aspect = object->aspects[i];
		if (!aspect)
			continue;
		object->aspects[i] = NULL;
		aspect_type = aspect->aspect_type;
		CHECK_PTR_NULL(aspect_type, done);
		if (aspect_type->exit_fn) {
			aspect_type->exit_fn(aspect);
		}
		if (aspect->shortcut)
			continue;
		brick_mem_free(aspect);
		atomic_dec(&object_layout->aspect_count);
	}
	if (object_type->exit_fn) {
		object_type->exit_fn(object);
	}
	brick_mem_free(object);
done: ;
}
EXPORT_SYMBOL_GPL(generic_free);

static
struct generic_aspect *_new_aspect(struct generic_brick *brick, struct generic_object *obj)
{
	struct generic_aspect *res = NULL;
	const struct generic_brick_type *brick_type = brick->type;
	const struct generic_object_type *object_type;
	const struct generic_aspect_type *aspect_type;
	int object_type_nr;
	int size;
	int rest;
	
	object_type = obj->object_type;
	CHECK_PTR_NULL(object_type, done);
	object_type_nr = object_type->object_type_nr;
	aspect_type = brick_type->aspect_types[object_type_nr];
	CHECK_PTR_NULL(aspect_type, done);
	
	size = aspect_type->aspect_size;
	rest = obj->max_offset - obj->free_offset;
	if (likely(size <= rest)) {
		/* Optimisation: re-use single memory allocation for both
		 * the object and the new aspect.
		 */
		res = ((void*)obj) + obj->free_offset;
		obj->free_offset += size;
		res->shortcut = true;
	} else {
		struct generic_object_layout *object_layout = obj->object_layout;
		CHECK_PTR(object_layout, done);
		/* Maintain the size hint.
		 * In future, only small aspects should be integrated into
		 * the same memory block, and the hint should not grow larger
		 * than PAGE_SIZE if it was smaller before.
		 */
		if (size < PAGE_SIZE / 2) {
			int max;
			max = obj->free_offset + size;
			/* This is racy, but races won't do any harm because
			 * it is just a hint, not essential.
			 */
			if ((max < PAGE_SIZE || object_layout->size_hint > PAGE_SIZE) && 
			    object_layout->size_hint < max)
				object_layout->size_hint = max;
		}

		res = brick_zmem_alloc(size);
		if (unlikely(!res)) {
			goto done;
		}
		atomic_inc(&object_layout->aspect_count);
		atomic_inc(&object_layout->total_aspect_count);
	}
	res->object = obj;
	res->aspect_type = aspect_type;

	if (aspect_type->init_fn) {
		int status = aspect_type->init_fn(res);
		if (unlikely(status < 0)) {
			BRICK_ERR("aspect init %p %p %p status = %d\n", brick, obj, res, status);
			goto done;
		}
	}

done:
	return res;
}

struct generic_aspect *generic_get_aspect(struct generic_brick *brick, struct generic_object *obj)
{
	struct generic_aspect *res = NULL;
	int nr;

	CHECK_PTR(brick, done);
	CHECK_PTR(obj, done);

	nr = brick->brick_index;
	if (unlikely(nr <= 0 || nr >= obj->aspect_nr_max)) {
		BRICK_ERR("bad nr = %d\n", nr);
		goto done;
	}

	res = obj->aspects[nr];
	if (!res) {
		res = _new_aspect(brick, obj);
		obj->aspects[nr] = res;
	}
	CHECK_PTR(res, done);
	CHECK_PTR(res->object, done);
	_CHECK(res->object == obj, done);

done:
	return res;
}
EXPORT_SYMBOL_GPL(generic_get_aspect);

/////////////////////////////////////////////////////////////////

// helper stuff

void set_button(struct generic_switch *sw, bool val, bool force)
{
	bool oldval = sw->button;
	if ((sw->force_off |= force))
		val = false;
	if (val != oldval) {
		sw->button = val;
		//sw->trigger = true;
		wake_up_interruptible(&sw->event);
	}
}
EXPORT_SYMBOL_GPL(set_button);

void set_led_on(struct generic_switch *sw, bool val)
{
	bool oldval = sw->led_on;
	if (val != oldval) {
		sw->led_on = val;
		//sw->trigger = true;
		wake_up_interruptible(&sw->event);
	}
}
EXPORT_SYMBOL_GPL(set_led_on);

void set_led_off(struct generic_switch *sw, bool val)
{
	bool oldval = sw->led_off;
	if (val != oldval) {
		sw->led_off = val;
		//sw->trigger = true;
		wake_up_interruptible(&sw->event);
	}
}
EXPORT_SYMBOL_GPL(set_led_off);

void set_button_wait(struct generic_brick *brick, bool val, bool force, int timeout)
{
	set_button(&brick->power, val, force);
	if (brick->ops)
		(void)brick->ops->brick_switch(brick);
	if (val) {
		wait_event_interruptible_timeout(brick->power.event, brick->power.led_on, timeout);
	} else {
		wait_event_interruptible_timeout(brick->power.event, brick->power.led_off, timeout);
	}
}
EXPORT_SYMBOL_GPL(set_button_wait);

/////////////////////////////////////////////////////////////////

// meta stuff

const struct meta *find_meta(const struct meta *meta, const char *field_name)
{
	const struct meta *tmp;
	for (tmp = meta; tmp->field_name; tmp++) {
		if (!strcmp(field_name, tmp->field_name)) {
			return tmp;
		}
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(find_meta);

#if 0 // currently not needed, but this may change
void free_meta(void *data, const struct meta *meta)
{
	for (; meta->field_name[0]; meta++) {
		void *item;
		switch (meta->field_type) {
		case FIELD_SUB:
			if (meta->field_ref) {
				item = data + meta->field_offset;
				free_meta(item, meta->field_ref);
			}
			break;
		case FIELD_REF:
		case FIELD_STRING:
			item = data + meta->field_offset;
			item = *(void**)item;
			if (meta->field_ref)
				free_meta(item, meta->field_ref);
			brick_mem_free(item);
		}
	}
}
EXPORT_SYMBOL_GPL(free_meta);
#endif

/////////////////////////////////////////////////////////////////////////

// module init stuff

int __init init_brick(void)
{
	nr_table = brick_zmem_alloc(nr_max);
	if (!nr_table) {
		return -ENOMEM;
	}
	return 0;
}

void exit_brick(void)
{
	if (nr_table) {
		brick_mem_free(nr_table);
		nr_table = NULL;
	}
}