/* $Id$ */
/* Copyright (c) 2011-2022 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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <System.h>
#include "App.h"

#ifndef PROGNAME
# define PROGNAME "AppClient"
#endif


/* AppClient */
/* private */
/* types */
typedef enum _AppClientCallArgType
{
	ACCAT_DOUBLE = 0, ACCAT_FLOAT, ACCAT_INTEGER, ACCAT_STRING
} AppClientCallArgType;

typedef struct _AppClientCallArg
{
	AppClientCallArgType type;
	double _double;
	float _float;
	int integer;
	char const * string;
} AppClientCallArg;

typedef struct _AppClientCall
{
	char const * name;
	AppClientCallArg * args;
	size_t args_cnt;
} AppClientCall;


/* prototypes */
static int _appclient(int verbose, char const * app, char const * name,
		AppClientCall calls[], size_t calls_cnt);

static int _error(char const * message, int ret);


/* functions */
static int _appclient_call(int verbose, AppClient * ac, AppClientCall * call);

static int _appclient(int verbose, char const * app, char const * name,
		AppClientCall calls[], size_t calls_cnt)
{
	int ret = 0;
	AppClient * ac;
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%d, %s, %s, %p, %zu)\n", __func__, verbose,
			app, name, (void *)calls, calls_cnt);
#endif
	if((ac = appclient_new(NULL, app, name)) == NULL)
		return _error(PROGNAME, 1);
	if(verbose != 0)
		puts("Connected.");
	for(i = 0; i < calls_cnt; i++)
		if(_appclient_call(verbose, ac, &calls[i]) != 0)
		{
			ret |= _error(PROGNAME, 1);
			break;
		}
	if(verbose != 0)
		puts("Disconnecting");
	appclient_delete(ac);
	return ret;
}

static int _appclient_call(int verbose, AppClient * ac, AppClientCall * call)
{
	int ret = 0;
	Variable * v;
	VariableType type;
	int64_t res;
	double dres;
	String * sres;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(verbose != 0)
		printf("Calling %s() with %lu arguments\n", call->name,
				(unsigned long)call->args_cnt);
	if((v = variable_new(VT_NULL, NULL)) == NULL)
		return -1;
	/* FIXME may segfault (check interface), use appclient_callv? */
	switch(call->args_cnt)
	{
		case 0:
#ifdef DEBUG
			fprintf(stderr, "DEBUG: %s() %s() res=%ld\n", __func__,
					call->name, res);
#endif
			ret = appclient_call_variable(ac, v, call->name, NULL);
			break;
		case 1:
#ifdef DEBUG
			fprintf(stderr, "DEBUG: %s() %s(%d)\n", __func__,
					call->name, call->args[0].integer);
#endif
			if(call->args[0].type == ACCAT_DOUBLE)
				ret = appclient_call_variable(ac, v, call->name,
						call->args[0]._double);
			else if(call->args[0].type == ACCAT_FLOAT)
				ret = appclient_call_variable(ac, v, call->name,
						call->args[0]._float);
			else if(call->args[0].type == ACCAT_INTEGER)
				ret = appclient_call_variable(ac, v, call->name,
						call->args[0].integer);
			else if(call->args[0].type == ACCAT_STRING)
				ret = appclient_call_variable(ac, v, call->name,
						call->args[0].string);
			else
				ret = error_set_code(1, "%s",
						"Unsupported types");
			break;
		case 2:
			/* FIXME arguments may be of different types */
#ifdef DEBUG
			fprintf(stderr, "DEBUG: %s() %s(%d, %d)\n", __func__,
					call->name,
					call->args[0].integer,
					call->args[1].integer);
#endif
			ret = appclient_call_variable(ac, v, call->name,
					call->args[0].integer,
					call->args[1].integer);
			break;
		case 3:
			if(strcmp(call->name, "glTranslatef") == 0)
			{
#ifdef DEBUG
				fprintf(stderr, "DEBUG: %s() %s(%.1f, %.1f,"
						" %.1f)\n", __func__,
						call->name,
						call->args[0]._float,
						call->args[1]._float,
						call->args[2]._float);
#endif
				ret = appclient_call_variable(ac, v, call->name,
						call->args[0]._float,
						call->args[1]._float,
						call->args[2]._float);
				break;
			}
			/* FIXME arguments may be of different types */
#ifdef DEBUG
			fprintf(stderr, "DEBUG: %s() %s(%d, %d, %d)\n",
					__func__, call->name,
					call->args[0].integer,
					call->args[1].integer,
					call->args[2].integer);
#endif
			ret = appclient_call_variable(ac, v, call->name,
					call->args[0].integer,
					call->args[1].integer,
					call->args[2].integer);
			break;
		case 4:
#ifdef DEBUG
			fprintf(stderr, "DEBUG: %s() %s(%.1f, %.1f, %.1f,"
					" %.1f)\n", __func__, call->name,
					call->args[0]._float,
					call->args[1]._float,
					call->args[2]._float,
					call->args[3]._float);
#endif
			ret = appclient_call_variable(ac, v, call->name,
					call->args[0]._float,
					call->args[1]._float,
					call->args[2]._float,
					call->args[3]._float);
			break;
		default:
			return error_set_code(1, "%s",
					"Unsupported number of arguments");
	}
	if(ret == 0 && verbose)
	{
		type = (v != NULL) ? variable_get_type(v) : VT_NULL;
		switch(type)
		{
			case VT_BOOL:
			case VT_INT8:
			case VT_UINT8:
			case VT_INT16:
			case VT_UINT16:
			case VT_INT32:
			case VT_UINT32:
			case VT_INT64:
			case VT_UINT64:
				if(variable_get_as(v, VT_INT64, &res, NULL)
						== 0)
					printf("\"%s\"%s%ld\n", call->name,
							" returned ", res);
				else
					printf("\"%s\"%s\n", call->name,
							" returned");
				break;
			case VT_FLOAT:
			case VT_DOUBLE:
				if(variable_get_as(v, VT_DOUBLE, &dres, NULL)
						== 0)
					printf("\"%s\"%s%f\n", call->name,
							" returned ", dres);
				else
					printf("\"%s\"%s\n", call->name,
							" returned");
				break;
			case VT_STRING:
				sres = NULL;
				if(variable_get_as(v, VT_STRING, &sres, NULL)
						== 0)
					printf("\"%s\"%s\"%s\"\n", call->name,
							" returned ", sres);
				else
					printf("\"%s\"%s\n", call->name,
							" returned");
				string_delete(sres);
				break;
			default:
				printf("\"%s\"%s\n", call->name, " returned");
				break;
		}
	}
	if(v != NULL)
		variable_delete(v);
	return ret;
}


/* error */
static int _error(char const * message, int ret)
{
	error_print(message);
	return ret;
}


/* usage */
static int _usage(void)
{
	fputs("Usage: " PROGNAME " [-v][-H hostname] -S service"
" [-C call [-d double|-f float|-i integer|-s string]...]...\n"
"  -v	Be more verbose\n"
"  -H	Hostname to connect to\n"
"  -S	Service to connect to\n"
"  -C	Enqueue a given call\n"
"  -d	Add a double as an argument to the current call\n"
"  -f	Add a float as an argument to the current call\n"
"  -i	Add an integer as an argument to the current call\n"
"  -s	Add a string as an argument to the current call\n", stderr);
	return 1;
}


/* main */
static int _main_call(AppClientCall ** calls, size_t * calls_cnt,
		char const * name);
static int _main_call_arg(AppClientCall * calls, size_t calls_cnt,
		AppClientCallArgType type, char const * string);

int main(int argc, char * argv[])
{
	int o;
	int res;
	int verbose = 0;
	char const * app = NULL;
	char const * name = NULL;
	AppClientCall * calls = NULL;
	size_t calls_cnt = 0;

	while((o = getopt(argc, argv, "vH:S:C:d:f:i:s:")) != -1)
	{
		res = 0;
		switch(o)
		{
			case 'v':
				verbose = 1;
				break;
			case 'H':
				name = optarg;
				break;
			case 'S':
				app = optarg;
				break;
			case 'C':
				res = _main_call(&calls, &calls_cnt, optarg);
				break;
			case 'd':
				res = _main_call_arg(calls, calls_cnt,
						ACCAT_DOUBLE, optarg);
				break;
			case 'f':
				res = _main_call_arg(calls, calls_cnt,
						ACCAT_FLOAT, optarg);
				break;
			case 'i':
				res = _main_call_arg(calls, calls_cnt,
						ACCAT_INTEGER, optarg);
				break;
			case 's':
				res = _main_call_arg(calls, calls_cnt,
						ACCAT_STRING, optarg);
				break;
			default:
				return _usage();
		}
		if(res != 0)
			return _error(PROGNAME, 2);
	}
	if(app == NULL)
		return _usage();
	return (_appclient(verbose, app, name, calls, calls_cnt) == 0) ? 0 : 2;
}

static int _main_call(AppClientCall ** calls, size_t * calls_cnt,
		char const * name)
{
	AppClientCall * p;

	if((p = realloc(*calls, sizeof(*p) * ((*calls_cnt) + 1))) == NULL)
		return error_set_code(1, "%s", strerror(errno));
	*calls = p;
	p = &(*calls)[(*calls_cnt)++];
	memset(p, 0, sizeof(*p));
	p->name = name;
	p->args = NULL;
	p->args_cnt = 0;
	return 0;
}

static int _main_call_arg(AppClientCall * calls, size_t calls_cnt,
		AppClientCallArgType type, char const * string)
{
	AppClientCall * p;
	AppClientCallArg * q;

	if(calls_cnt == 0)
		return 1; /* FIXME report error */
	p = &calls[calls_cnt - 1];
	if((q = realloc(p->args, sizeof(*q) * (p->args_cnt + 1))) == NULL)
		return error_set_code(1, "%s", strerror(errno));
	p->args = q;
	q = &q[p->args_cnt++];
	memset(q, 0, sizeof(*q));
	q->type = type;
	switch(type)
	{
		case ACCAT_DOUBLE:
			q->_double = strtod(string, NULL); /* XXX check */
			break;
		case ACCAT_FLOAT:
			q->_float = strtof(string, NULL); /* XXX check */
			break;
		case ACCAT_INTEGER:
			q->integer = strtol(string, NULL, 0); /* XXX check */
			break;
		case ACCAT_STRING:
			q->string = string;
			break;
	}
	return 0;
}
