/* $Id$ */
/* Copyright (c) 2011-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 <sys/types.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <System.h>
#include "App/appclient.h"
#include "App/appmessage.h"
#include "apptransport.h"
#include "appinterface.h"


/* AppClient */
/* private */
/* types */
struct _AppClient
{
	App * app;
	AppInterface * interface;
	Event * event;
	int event_free;
	AppTransport * transport;
	AppTransportHelper helper;
};


/* prototypes */
/* helpers */
static int _appclient_helper_message(void * data, AppTransport * transport,
		AppTransportClient * client, AppMessage * message);


/* public */
/* functions */
/* appclient_new */
AppClient * appclient_new(App * self, char const * app, char const * name)
{
	return appclient_new_event(self, app, name, NULL);
}


/* appclient_new_event */
AppClient * appclient_new_event(App * self, char const * app,
		char const * name, Event * event)
{
	AppClient * appclient;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\", %p)\n", __func__, app, name,
			(void *)event);
#endif
	if((appclient = object_new(sizeof(*appclient))) == NULL)
		return NULL;
	appclient->app = self;
	appclient->interface = appinterface_new(ATM_CLIENT, app);
	appclient->helper.data = appclient;
	appclient->helper.message = _appclient_helper_message;
	appclient->event = (event != NULL) ? event : event_new();
	appclient->event_free = (event != NULL) ? 0 : 1;
	appclient->transport = apptransport_new_app(ATM_CLIENT,
			&appclient->helper, app, name, appclient->event);
	/* check for errors */
	if(appclient->interface == NULL
			|| appclient->transport == NULL
			|| appclient->event == NULL)
	{
		appclient_delete(appclient);
#ifdef DEBUG
		fprintf(stderr, "DEBUG: %s() => NULL\n", __func__);
#endif
		return NULL;
	}
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", %p) => %p\n", __func__, app,
			(void *)event, (void *)appclient);
#endif
	return appclient;
}


/* appclient_delete */
void appclient_delete(AppClient * appclient)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(appclient->interface != NULL)
		appinterface_delete(appclient->interface);
	if(appclient->event_free != 0)
		event_delete(appclient->event);
	object_delete(appclient);
}


/* accessors */
/* appclient_get_app */
char const * appclient_get_app(AppClient * appclient)
{
	return appinterface_get_app(appclient->interface);
}


/* appclient_get_status */
AppStatus * appclient_get_status(AppClient * appclient)
{
	return appinterface_get_status(appclient->interface);
}


/* useful */
/* appclient_call */
int appclient_call(AppClient * appclient,
		void ** result, char const * method, ...)
{
	int ret;
	va_list ap;

	va_start(ap, method);
	ret = appclient_callv(appclient, result, method, ap);
	va_end(ap);
	return ret;
}


/* appclient_callv */
int appclient_callv(AppClient * appclient,
		void ** result, char const * method, va_list ap)
{
	int ret;
	AppMessage * message;

	if((message = appinterface_messagev(appclient->interface, method, ap))
			== NULL)
		return -1;
	/* FIXME obtain the answer (AICD_{,IN}OUT) */
	ret = apptransport_client_send(appclient->transport, message, 1);
	appmessage_delete(message);
	return ret;
}


/* appclient_call_variable */
int appclient_call_variable(AppClient * appclient,
		Variable * result, char const * method, ...)
{
	int ret;
	va_list ap;

	va_start(ap, method);
	ret = appclient_call_variablev(appclient, result, method, ap);
	va_end(ap);
	return ret;
}


/* appclient_call_variables */
int appclient_call_variables(AppClient * appclient,
		Variable * result, char const * method, Variable ** args)
{
	int ret;
	AppMessage * message;

	if((message = appinterface_message_variables(appclient->interface,
					method, args)) == NULL)
		return -1;
	/* FIXME obtain the answer (AICD_{,IN}OUT) */
	ret = apptransport_client_send(appclient->transport, message, 1);
	appmessage_delete(message);
	return ret;
}


/* appclient_call_variablev */
int appclient_call_variablev(AppClient * appclient,
		Variable * result, char const * method, va_list args)
{
	int ret;
	AppMessage * message;

	if((message = appinterface_message_variablev(appclient->interface,
					method, args)) == NULL)
		return -1;
	/* FIXME obtain the answer (AICD_{,IN}OUT) */
	ret = apptransport_client_send(appclient->transport, message, 1);
	appmessage_delete(message);
	return ret;
}


/* private */
/* appclient_helper_message */
static int _helper_message_call(AppClient * appclient, AppTransport * transport,
		AppMessage * message);

static int _appclient_helper_message(void * data, AppTransport * transport,
		AppTransportClient * client, AppMessage * message)
{
	AppClient * appclient = data;

	if(client != NULL)
		/* XXX report error */
		return -1;
	switch(appmessage_get_type(message))
	{
		case AMT_CALL:
			return _helper_message_call(appclient, transport,
					message);
	}
	/* FIXME implement */
	return -1;
}

static int _helper_message_call(AppClient * appclient, AppTransport * transport,
		AppMessage * message)
{
	/* we have received a callback request */
	int ret;
	String const * method;
	String const * name;
	Variable * result = NULL;

	method = appmessage_get_method(message);
	name = apptransport_get_name(transport);
	if(!appinterface_can_call(appclient->interface, method, name))
		/* XXX report errors */
		return -1;
	ret = appinterface_call_variablev(appclient->interface, appclient->app,
			NULL, result, method, 0, NULL);
	if(result != NULL)
		variable_delete(result);
	return ret;
}
