/* $Id$ */
/* Copyright (c) 2012-2020 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS System libApp */
/* 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, version 3 of the License.
 *
 * 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, see <http://www.gnu.org/licenses/>. */



#include <stdlib.h>
#ifdef DEBUG
# include <stdio.h>
#endif
#include <string.h>
#include <System.h>
#include "App/appclient.h"
#include "appmessage.h"
#include "apptransport.h"
#include "../config.h"
/* FIXME:
 * clarify parsing name strings (if there is a colon, there is a transport,
 * although possibly empty) */

#ifndef PREFIX
# define PREFIX		"/usr/local"
#endif
#ifndef LIBDIR
# define LIBDIR		PREFIX "/lib"
#endif


/* AppTransport */
/* private */
/* types */
struct _AppTransport
{
	AppTransportMode mode;
	AppTransportHelper helper;
	String * name;

	/* registration */
	AppClient * appclient;

	/* plug-in */
	AppTransportPluginHelper thelper;
	Plugin * plugin;
	AppTransportPlugin * tplugin;
	AppTransportPluginDefinition * definition;

	/* acknowledgements */
	AppMessageID id;
};

struct _AppTransportClient
{
	AppTransport * transport;
	String * name;
};


/* prototypes */
/* helpers */
static int _apptransport_helper_status(AppTransport * transport,
		AppTransportStatus status, unsigned int code,
		char const * message);

static AppTransportClient * _apptransport_helper_client_new(
		AppTransport * transport, char const * name);
static void _apptransport_helper_client_delete(AppTransport * transport,
		AppTransportClient * client);

static int _apptransport_helper_client_receive(AppTransport * transport,
		AppTransportClient * client, AppMessage * message);


/* protected */
/* functions */
/* apptransport_new */
AppTransport * apptransport_new(AppTransportMode mode,
		AppTransportHelper * helper, char const * transport,
		char const * name, Event * event)
{
	AppTransport * apptransport;
	Plugin * plugin;

	if((plugin = plugin_new(LIBDIR, "App", "transport", transport)) == NULL)
		return NULL;
	if((apptransport = apptransport_new_plugin(mode, helper, plugin,
					name, event)) == NULL)
	{
		plugin_delete(plugin);
		return NULL;
	}
	return apptransport;
}


/* apptransport_new_app */
static String * _new_app_name(AppTransportMode mode, char const * app,
		char const * name);
static String * _new_app_transport(String ** name);

AppTransport * apptransport_new_app(AppTransportMode mode,
		AppTransportHelper * helper, char const * app,
		char const * name, Event * event)
{
	AppTransport * apptransport;
	String * n;
	String * transport;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%u, \"%s\", \"%s\")\n", __func__, mode, app,
			name);
#endif
	if((n = _new_app_name(mode, app, name)) == NULL)
		return NULL;
	if((transport = _new_app_transport(&n)) == NULL)
	{
		string_delete(n);
		return NULL;
	}
	apptransport = apptransport_new(mode, helper, transport, n, event);
	string_delete(transport);
	string_delete(n);
	return apptransport;
}

static String * _new_app_name(AppTransportMode mode, char const * app,
		char const * name)
{
	String * var;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\")\n", __func__, app, name);
#endif
	if(app == NULL || app[0] == '\0')
	{
		error_set_code(1, "%s", "Invalid App");
		return NULL;
	}
	if(name != NULL)
		return string_new(name);
	/* obtain the desired transport and name from the environment */
	if((var = string_new_append("APPSERVER_", app, NULL)) == NULL)
		return NULL;
	name = getenv(var);
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() \"%s\" \"%s\"\n", __func__, var, name);
#endif
	string_delete(var);
	if(name == NULL)
	{
		if(mode == ATM_CLIENT)
			return apptransport_lookup(app);
		error_set_code(1, "%s: %s", app, "Could not lookup name");
		return NULL;
	}
	return string_new(name);
}

static String * _new_app_transport(String ** name)
{
	String * p;
	String * transport;

	if((p = strchr(*name, ':')) == NULL)
		/* XXX hard-coded default value */
		return string_new("tcp");
	/* XXX */
	*p = '\0';
	transport = string_new(*name);
	*p = ':';
	if(transport == NULL || (*name = string_new(++p)) == NULL)
	{
		string_delete(transport);
		return NULL;
	}
	return transport;
}


/* apptransport_new_plugin */
static void _new_plugin_helper(AppTransport * apptransport,
		AppTransportMode mode, Event * event);

AppTransport * apptransport_new_plugin(AppTransportMode mode,
		AppTransportHelper * helper, Plugin * plugin,
		char const * name, Event * event)
{
	AppTransport * apptransport;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%u, \"%s\", \"%s\", %p)\n", __func__, mode,
			plugin, name, (void *)event);
#endif
	/* check the arguments */
	if(plugin == NULL)
	{
		error_set_code(1, "%s", "Invalid transport plug-in");
		return NULL;
	}
	if(name == NULL || event == NULL)
	{
		error_set_code(1, "%s", "Invalid arguments");
		return NULL;
	}
	/* allocate the transport */
	if((apptransport = object_new(sizeof(*apptransport))) == NULL)
		return NULL;
	memset(apptransport, 0, sizeof(*apptransport));
	apptransport->mode = mode;
	if(helper != NULL)
		apptransport->helper = *helper;
	apptransport->name = string_new(name);
	/* initialize the plug-in helper */
	_new_plugin_helper(apptransport, mode, event);
	/* load the transport plug-in */
	apptransport->plugin = plugin;
	if(apptransport->name == NULL
			|| (apptransport->definition = plugin_lookup(
					apptransport->plugin, "transport"))
			== NULL
			|| apptransport->definition->init == NULL
			|| apptransport->definition->destroy == NULL
			|| (apptransport->tplugin
				= apptransport->definition->init(
					&apptransport->thelper, mode, name))
			== NULL)
	{
		apptransport->plugin = NULL;
		apptransport_delete(apptransport);
		return NULL;
	}
	return apptransport;
}

static void _new_plugin_helper(AppTransport * apptransport,
		AppTransportMode mode, Event * event)
{
	apptransport->thelper.transport = apptransport;
	apptransport->thelper.event = event;
	apptransport->thelper.status = _apptransport_helper_status;
	apptransport->thelper.client_new = _apptransport_helper_client_new;
	apptransport->thelper.client_delete
		= _apptransport_helper_client_delete;
	apptransport->thelper.client_receive
		= _apptransport_helper_client_receive;
}


/* apptransport_delete */
void apptransport_delete(AppTransport * transport)
{
	if(transport->appclient != NULL)
		appclient_delete(transport->appclient);
	if(transport->tplugin != NULL)
		transport->definition->destroy(transport->tplugin);
	if(transport->plugin != NULL)
		plugin_delete(transport->plugin);
	if(transport->name != NULL)
		string_delete(transport->name);
	object_delete(transport);
}


/* accessors */
/* apptransport_get_mode */
AppTransportMode apptransport_get_mode(AppTransport * transport)
{
	return transport->mode;
}


/* apptransport_get_name */
String const * apptransport_get_name(AppTransport * transport)
{
	return transport->name;
}


/* apptransport_get_transport */
String const * apptransport_get_transport(AppTransport * transport)
{
	return transport->definition->name;
}


/* apptransport_client_get_name */
String const * apptransport_client_get_name(AppTransportClient * client)
{
	return client->name;
}


/* useful */
/* apptransport_lookup */
String * apptransport_lookup(char const * app)
{
	const char session[] = "Session";
	String * name = NULL;
	AppClient * appclient;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, app);
#endif
	if(strcmp(app, session) == 0)
	{
		error_set_code(1, "%s: %s", app, "Could not lookup");
		return NULL;
	}
	if((appclient = appclient_new(NULL, session, NULL)) == NULL)
		return NULL;
	appclient_call(appclient, (void **)&name, "lookup", app);
	appclient_delete(appclient);
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() => \"%s\"\n", __func__, name);
#endif
	if(name == NULL)
		error_set_code(1, "%s: %s", app, "Could not lookup");
	return name;
}


/* apptransport_client_send */
int apptransport_client_send(AppTransport * transport, AppMessage * message,
		int acknowledge)
{
	if(transport->mode == ATM_CLIENT
			&& appmessage_get_type(message) == AMT_CALL
			&& acknowledge != 0)
		/* FIXME will wrap around after 2^32-1 acknowledgements */
		appmessage_set_id(message, ++transport->id);
	return transport->definition->client_send(transport->tplugin, message);
}


/* apptransport_server_register */
int apptransport_server_register(AppTransport * transport, char const * app,
		char const * name)
{
	int ret;
	int res = -1;
	const char session[] = "Session";

	if(transport->mode != ATM_SERVER)
		return -error_set_code(1, "%s",
				"Only servers can register to sessions");
	if(transport->appclient != NULL)
		appclient_delete(transport->appclient);
	if((transport->appclient = appclient_new(NULL, session, name)) == NULL)
		return -1;
	ret = appclient_call(transport->appclient, (void **)&res, "register",
			app, transport->name);
	ret = (ret == 0 && res == 0) ? 0 : -1;
	return ret;
}


/* apptransport_server_send */
int apptransport_server_send(AppTransport * transport,
		AppTransportClient * client, AppMessage * message)
{
	if(transport->mode != ATM_SERVER)
		return -error_set_code(1, "%s",
				"Only servers can reply to clients");
	if(transport->definition->server_send == NULL)
		return -error_set_code(1, "%s",
				"This transport does not support replies");
	return transport->definition->server_send(transport->tplugin, client,
			message);
}


/* private */
/* functions */
/* helpers */
/* apptransport_helper_status */
static int _apptransport_helper_status(AppTransport * transport,
		AppTransportStatus status, unsigned int code,
		char const * message)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%u, %u, \"%s\")\n", __func__, status, code,
			message);
#endif
	/* FIXME really implement */
	return 0;
}


/* apptransport_helper_client_new */
static AppTransportClient * _apptransport_helper_client_new(
		AppTransport * transport, char const * name)
{
	AppTransportClient * client;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if((client = object_new(sizeof(*client))) == NULL)
		return NULL;
	client->transport = transport;
	if(name != NULL)
	{
		if((client->name = string_new(name)) == NULL)
		{
			object_delete(client);
			return NULL;
		}
	}
	else
		client->name = NULL;
	return client;
}


/* apptransport_helper_client_delete */
static void _apptransport_helper_client_delete(AppTransport * transport,
		AppTransportClient * client)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	object_delete(client);
}


/* apptransport_helper_client_receive */
static int _apptransport_helper_client_receive(AppTransport * transport,
		AppTransportClient * client, AppMessage * message)
{
	AppMessageID id;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() %u %u \"%s\"\n", __func__,
			appmessage_get_type(message),
			appmessage_get_id(message),
			appmessage_get_method(message));
#endif
	if(transport->mode != ATM_SERVER)
		/* XXX improve the error message */
		return -error_set_code(1, "Not a server");
	/* XXX check for errors? */
	transport->helper.message(transport->helper.data, transport, client,
			message);
	/* check if an acknowledgement is requested */
	if((id = appmessage_get_id(message)) != 0)
		/* XXX we can ignore errors */
		if((message = appmessage_new_acknowledgement(id)) != NULL)
		{
			apptransport_server_send(transport, client, message);
			appmessage_delete(message);
		}
	return 0;
}
