/*
 * Copyright (c) 2011 Petr Koupy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup graph
 * @{
 */
/**
 * @file
 */

#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <malloc.h>
#include <as.h>
#include "graph.h"

#define NAMESPACE         "graphemu"
#define VISUALIZER_NAME   "vsl"
#define RENDERER_NAME     "rnd"

static sysarg_t namespace_idx = 0;
static sysarg_t visualizer_idx = 0;
static sysarg_t renderer_idx = 0;

static LIST_INITIALIZE(visualizer_list);
static LIST_INITIALIZE(renderer_list);

static FIBRIL_MUTEX_INITIALIZE(visualizer_list_mtx);
static FIBRIL_MUTEX_INITIALIZE(renderer_list_mtx);

visualizer_t *graph_alloc_visualizer(void)
{
	visualizer_t *vs = (visualizer_t *) malloc(sizeof(visualizer_t));
	if (vs == NULL) {
		return NULL;
	}
	
	return vs;
}

renderer_t *graph_alloc_renderer(void)
{
	// TODO
	renderer_t *rnd = (renderer_t *) malloc(sizeof(renderer_t));
	if (rnd == NULL) {
		return NULL;
	}

	return rnd;
}

void graph_init_visualizer(visualizer_t *vs)
{
	link_initialize(&vs->link);
	atomic_set(&vs->ref_cnt, 0);
	vs->notif_sess = NULL;
	fibril_mutex_initialize(&vs->mode_mtx);
	list_initialize(&vs->modes);
	vs->mode_set = false;
	vs->cells.data = NULL;
	vs->dev_ctx = NULL;
}

void graph_init_renderer(renderer_t *rnd)
{
	// TODO
	link_initialize(&rnd->link);
	atomic_set(&rnd->ref_cnt, 0);
}

int graph_register_visualizer(visualizer_t *vs)
{
	int rc = EOK;
	
	char node[LOC_NAME_MAXLEN + 1];
	snprintf(node, LOC_NAME_MAXLEN, "%s%zu/%s%zu", NAMESPACE,
	    namespace_idx, VISUALIZER_NAME, visualizer_idx++);

	category_id_t cat;
	rc = loc_category_get_id("visualizer", &cat, 0);
	if (rc != EOK) {
		return rc;
	}

	rc = loc_service_register(node, &vs->reg_svc_handle);
	if (rc != EOK) {
		return rc;
	}

	rc = loc_service_add_to_cat(vs->reg_svc_handle, cat);
	if (rc != EOK) {
		loc_service_unregister(vs->reg_svc_handle);
		return rc;
	}

	fibril_mutex_lock(&visualizer_list_mtx);
	list_append(&vs->link, &visualizer_list);
	fibril_mutex_unlock(&visualizer_list_mtx);

	return rc;
}

int graph_register_renderer(renderer_t *rnd)
{
	int rc = EOK;

	char node[LOC_NAME_MAXLEN + 1];
	snprintf(node, LOC_NAME_MAXLEN, "%s%zu/%s%zu", NAMESPACE,
	    namespace_idx, RENDERER_NAME, renderer_idx++);

	category_id_t cat;
	rc = loc_category_get_id("renderer", &cat, 0);
	if (rc != EOK) {
		return rc;
	}

	rc = loc_service_register(node, &rnd->reg_svc_handle);
	if (rc != EOK) {
		return rc;
	}

	rc = loc_service_add_to_cat(rnd->reg_svc_handle, cat);
	if (rc != EOK) {
		loc_service_unregister(rnd->reg_svc_handle);
		return rc;
	}

	fibril_mutex_lock(&renderer_list_mtx);
	list_append(&rnd->link, &renderer_list);
	fibril_mutex_unlock(&renderer_list_mtx);

	return rc;
}

visualizer_t *graph_get_visualizer(sysarg_t handle)
{
	visualizer_t *vs = NULL;

	fibril_mutex_lock(&visualizer_list_mtx);
	list_foreach(visualizer_list, link) {
		visualizer_t *cur = list_get_instance(link, visualizer_t, link);
		if (cur->reg_svc_handle == handle) {
			vs = cur;
			break;
		}
	}
	fibril_mutex_unlock(&visualizer_list_mtx);

	return vs;
}

renderer_t *graph_get_renderer(sysarg_t handle)
{
	renderer_t *rnd = NULL;

	fibril_mutex_lock(&renderer_list_mtx);
	list_foreach(renderer_list, link) {
		renderer_t *cur = list_get_instance(link, renderer_t, link);
		if (cur->reg_svc_handle == handle) {
			rnd = cur;
			break;
		}
	}
	fibril_mutex_unlock(&renderer_list_mtx);

	return rnd;
}

int graph_unregister_visualizer(visualizer_t *vs)
{
	int rc = EOK;

	fibril_mutex_lock(&visualizer_list_mtx);
	rc = loc_service_unregister(vs->reg_svc_handle);
	list_remove(&vs->link);
	fibril_mutex_unlock(&visualizer_list_mtx);

	return rc;
}

int graph_unregister_renderer(renderer_t *rnd)
{
	int rc = EOK;

	fibril_mutex_lock(&renderer_list_mtx);
	rc = loc_service_unregister(rnd->reg_svc_handle);
	list_remove(&rnd->link);
	fibril_mutex_unlock(&renderer_list_mtx);

	return rc;
}

void graph_destroy_visualizer(visualizer_t *vs)
{
	assert(atomic_get(&vs->ref_cnt) == 0);
	assert(vs->notif_sess == NULL);
	assert(!fibril_mutex_is_locked(&vs->mode_mtx));
	assert(list_empty(&vs->modes));
	assert(vs->mode_set == false);
	assert(vs->cells.data == NULL);
	assert(vs->dev_ctx == NULL);

	free(vs);
}

void graph_destroy_renderer(renderer_t *rnd)
{
	// TODO
	assert(atomic_get(&rnd->ref_cnt) == 0);

	free(rnd);
}

int graph_notify_mode_change(async_sess_t *sess, sysarg_t handle, sysarg_t mode_idx)
{
	async_exch_t *exch = async_exchange_begin(sess);
	int ret = async_req_2_0(exch, VISUALIZER_MODE_CHANGE, handle, mode_idx);
	async_exchange_end(exch);

	return ret;
}

int graph_notify_disconnect(async_sess_t *sess, sysarg_t handle)
{
	async_exch_t *exch = async_exchange_begin(sess);
	int ret = async_req_1_0(exch, VISUALIZER_DISCONNECT, handle);
	async_exchange_end(exch);

	async_hangup(sess);

	return ret;
}

static void vs_claim(visualizer_t *vs, ipc_callid_t iid, ipc_call_t *icall)
{
	vs->client_side_handle = IPC_GET_ARG1(*icall);
	int rc = vs->ops.claim(vs);
	async_answer_0(iid, rc);
}

static void vs_yield(visualizer_t *vs, ipc_callid_t iid, ipc_call_t *icall)
{
	/* Deallocate resources for the current mode. */
	if (vs->mode_set) {
		if (vs->cells.data != NULL) {
			as_area_destroy((void *) vs->cells.data);
			vs->cells.data = NULL;
		}
	}

	/* Driver might also deallocate resources for the current mode. */
	int rc = vs->ops.yield(vs);

	/* Now that the driver was given a chance to deallocate resources,
	 * current mode can be unset. */
	if (vs->mode_set) {
		vs->mode_set = false;
	}

	async_answer_0(iid, rc);
}

static void vs_enumerate_modes(visualizer_t *vs, ipc_callid_t iid, ipc_call_t *icall)
{
	fibril_mutex_lock(&vs->mode_mtx);
	link_t *link = list_nth(&vs->modes, IPC_GET_ARG1(*icall));

	if (link != NULL) {
		vslmode_list_element_t *mode_elem =
		    list_get_instance(link, vslmode_list_element_t, link);
		vslmode_t mode = mode_elem->mode;
		fibril_mutex_unlock(&vs->mode_mtx);

		ipc_callid_t callid;
		size_t len;

        if (!async_data_read_receive(&callid, &len)) {
			async_answer_0(iid, EINVAL);
			return;
        }
        int rc = async_data_read_finalize(callid, &mode, len);
		if (rc != EOK) {
			async_answer_0(iid, ENOMEM);
			return;
		}

		async_answer_0(iid, EOK);
	} else {
		async_answer_0(iid, ENOENT);
	}
}

static void vs_get_default_mode(visualizer_t *vs, ipc_callid_t iid, ipc_call_t *icall)
{
	fibril_mutex_lock(&vs->mode_mtx);
	vslmode_list_element_t *mode_elem = NULL;
	list_foreach(vs->modes, link) {
		vslmode_list_element_t *cur = list_get_instance(link, vslmode_list_element_t, link);
		if (cur->mode.index == vs->def_mode_idx) {
			mode_elem = cur;
			break;
		}
	}

	vslmode_t mode;
	if (mode_elem != NULL) {
		mode = mode_elem->mode;
		fibril_mutex_unlock(&vs->mode_mtx);

		ipc_callid_t callid;
		size_t len;

		if (!async_data_read_receive(&callid, &len)) {
			async_answer_0(iid, EINVAL);
			return;
		}
		int rc = async_data_read_finalize(callid, &mode, len);
		if (rc != EOK) {
			async_answer_0(iid, ENOMEM);
			return;
		}
		async_answer_0(iid, EOK);
	} else {
		fibril_mutex_unlock(&vs->mode_mtx);
		async_answer_0(iid, ENOENT);
	}
}

static void vs_get_current_mode(visualizer_t *vs, ipc_callid_t iid, ipc_call_t *icall)
{
	if (vs->mode_set) {
		ipc_callid_t callid;
		size_t len;

		if (!async_data_read_receive(&callid, &len)) {
			async_answer_0(iid, EINVAL);
			return;
		}
		int rc = async_data_read_finalize(callid, &vs->cur_mode, len);
		if (rc != EOK) {
			async_answer_0(iid, ENOMEM);
			return;
		}

		async_answer_0(iid, EOK);
	} else {
		async_answer_0(iid, ENOENT);
	}
}

static void vs_get_mode(visualizer_t *vs, ipc_callid_t iid, ipc_call_t *icall)
{
	sysarg_t mode_idx = IPC_GET_ARG1(*icall);

	fibril_mutex_lock(&vs->mode_mtx);
	vslmode_list_element_t *mode_elem = NULL;
	list_foreach(vs->modes, link) {
		vslmode_list_element_t *cur = list_get_instance(link, vslmode_list_element_t, link);
		if (cur->mode.index == mode_idx) {
			mode_elem = cur;
			break;
		}
	}

	vslmode_t mode;
	if (mode_elem != NULL) {
		mode = mode_elem->mode;
		fibril_mutex_unlock(&vs->mode_mtx);

		ipc_callid_t callid;
		size_t len;

		if (!async_data_read_receive(&callid, &len)) {
			async_answer_0(iid, EINVAL);
			return;
		}
		int rc = async_data_read_finalize(callid, &mode, len);
		if (rc != EOK) {
			async_answer_0(iid, ENOMEM);
			return;
		}
		async_answer_0(iid, EOK);
	} else {
		fibril_mutex_unlock(&vs->mode_mtx);
		async_answer_0(iid, ENOENT);
	}
}

static void vs_set_mode(visualizer_t *vs, ipc_callid_t iid, ipc_call_t *icall)
{
	int rc = EOK;

	/* Retrieve mode index and version. */
	sysarg_t mode_idx = IPC_GET_ARG1(*icall);
	sysarg_t mode_version = IPC_GET_ARG2(*icall);

	/* Find mode in the list. */
	fibril_mutex_lock(&vs->mode_mtx);
	vslmode_list_element_t *mode_elem = NULL;
	list_foreach(vs->modes, link) {
		vslmode_list_element_t *cur = list_get_instance(link, vslmode_list_element_t, link);
		if (cur->mode.index == mode_idx) {
			mode_elem = cur;
			break;
		}
	}

	/* Extract mode description from the list node. */
	vslmode_t new_mode;
	if (mode_elem != NULL) {
		new_mode = mode_elem->mode;
		fibril_mutex_unlock(&vs->mode_mtx);
	} else {
		fibril_mutex_unlock(&vs->mode_mtx);
		async_answer_0(iid, ENOENT);
		return;
	}

	/* Check whether the mode is still up-to-date. */
	if (new_mode.version != mode_version) {
		async_answer_0(iid, EINVAL);
		return;
	}

	ipc_callid_t callid;
	size_t size;
	unsigned int flags;

	/* Retrieve the shared cell storage for the new mode. */
	if (!async_share_out_receive(&callid, &size, &flags)) {
		async_answer_0(iid, EINVAL);
		return;
	}
	void *new_cell_storage;
	rc = async_share_out_finalize(callid, &new_cell_storage);
	if ((rc != EOK) || (new_cell_storage == AS_MAP_FAILED)) {
		async_answer_0(iid, ENOMEM);
		return;
	}

	/* Change device internal state. */
	rc = vs->ops.change_mode(vs, new_mode);

	/* Device driver could not establish new mode. Rollback. */
	if (rc != EOK) {
		as_area_destroy(new_cell_storage);
		async_answer_0(iid, ENOMEM);
		return;
	}

	/* Because resources for the new mode were successfully claimed,
	 * it is finally possible to free resources allocated for the old mode. */
	if (vs->mode_set) {
		if (vs->cells.data != NULL) {
			as_area_destroy((void *) vs->cells.data);
			vs->cells.data = NULL;
		}
	}

	/* Insert new mode into the visualizer. */
	vs->cells.width = new_mode.screen_width;
	vs->cells.height = new_mode.screen_height;
	vs->cells.data = (pixel_t *) new_cell_storage;
	vs->cur_mode = new_mode;
	vs->mode_set = true;

	async_answer_0(iid, EOK);
}

static void vs_update_damaged_region(visualizer_t *vs, ipc_callid_t iid, ipc_call_t *icall)
{
#ifdef __32_BITS__
	sysarg_t x_offset = (IPC_GET_ARG5(*icall) >> 16);
	sysarg_t y_offset = (IPC_GET_ARG5(*icall) & 0x0000ffff);
#else
	sysarg_t x_offset = (IPC_GET_ARG5(*icall) >> 32);
	sysarg_t y_offset = (IPC_GET_ARG5(*icall) & 0xffffffff);
#endif

	int rc = vs->ops.handle_damage(vs,
	    IPC_GET_ARG1(*icall), IPC_GET_ARG2(*icall),
	    IPC_GET_ARG3(*icall), IPC_GET_ARG4(*icall),
		x_offset, y_offset);
	async_answer_0(iid, rc);
}

static void vs_suspend(visualizer_t *vs, ipc_callid_t iid, ipc_call_t *icall)
{
	int rc = vs->ops.suspend(vs);
	async_answer_0(iid, rc);
}

static void vs_wakeup(visualizer_t *vs, ipc_callid_t iid, ipc_call_t *icall)
{
	int rc = vs->ops.wakeup(vs);
	async_answer_0(iid, rc);
}

void graph_visualizer_connection(visualizer_t *vs,
    ipc_callid_t iid, ipc_call_t *icall, void *arg)
{
	ipc_call_t call;
	ipc_callid_t callid;

	/* Claim the visualizer. */
	if (!cas(&vs->ref_cnt, 0, 1)) {
		async_answer_0(iid, ELIMIT);
		return;
	}

	/* Accept the connection. */
	async_answer_0(iid, EOK);

	/* Establish callback session. */
	callid = async_get_call(&call);
	vs->notif_sess = async_callback_receive_start(EXCHANGE_SERIALIZE, &call);
	if (vs->notif_sess != NULL) {
		async_answer_0(callid, EOK);
	} else {
		async_answer_0(callid, ELIMIT);
	}

	/* Enter command loop. */
	while (true) {
		callid = async_get_call(&call);

		if (!IPC_GET_IMETHOD(call)) {
			async_answer_0(callid, EINVAL);
			break;
		}

		switch (IPC_GET_IMETHOD(call)) {
		case VISUALIZER_CLAIM:
			vs_claim(vs, callid, &call);
			break;
		case VISUALIZER_YIELD:
			vs_yield(vs, callid, &call);
			goto terminate;
		case VISUALIZER_ENUMERATE_MODES:
			vs_enumerate_modes(vs, callid, &call);
			break;
		case VISUALIZER_GET_DEFAULT_MODE:
			vs_get_default_mode(vs, callid, &call);
			break;
		case VISUALIZER_GET_CURRENT_MODE:
			vs_get_current_mode(vs, callid, &call);
			break;
		case VISUALIZER_GET_MODE:
			vs_get_mode(vs, callid, &call);
			break;
		case VISUALIZER_SET_MODE:
			vs_set_mode(vs, callid, &call);
			break;
		case VISUALIZER_UPDATE_DAMAGED_REGION:
			vs_update_damaged_region(vs, callid, &call);
			break;
		case VISUALIZER_SUSPEND:
			vs_suspend(vs, callid, &call);
			break;
		case VISUALIZER_WAKE_UP:
			vs_wakeup(vs, callid, &call);
			break;
		default:
			async_answer_0(callid, EINVAL);
			goto terminate;
		}
	}

terminate:
	async_hangup(vs->notif_sess);
	vs->notif_sess = NULL;
	atomic_set(&vs->ref_cnt, 0);
}

void graph_renderer_connection(renderer_t *rnd,
    ipc_callid_t iid, ipc_call_t *icall, void *arg)
{
	// TODO

	ipc_call_t call;
	ipc_callid_t callid;

	/* Accept the connection. */
	atomic_inc(&rnd->ref_cnt);
	async_answer_0(iid, EOK);

	/* Enter command loop. */
	while (true) {
		callid = async_get_call(&call);

		if (!IPC_GET_IMETHOD(call)) {
			async_answer_0(callid, EINVAL);
			break;
		}

		switch (IPC_GET_IMETHOD(call)) {
		default:
			async_answer_0(callid, EINVAL);
			goto terminate;
		}
	}

terminate:
	atomic_dec(&rnd->ref_cnt);
}

void graph_client_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
{
	/* Find the visualizer or renderer with the given service ID. */
	visualizer_t *vs = graph_get_visualizer(IPC_GET_ARG1(*icall));
	renderer_t *rnd = graph_get_renderer(IPC_GET_ARG1(*icall));

	if (vs != NULL) {
		graph_visualizer_connection(vs, iid, icall, arg);
	} else if (rnd != NULL) {
		graph_renderer_connection(rnd, iid, icall, arg);
	} else {
		async_answer_0(iid, ENOENT);
	}
}

/** @}
 */
