/* $Id$ */
/* Copyright (c) 2008-2018 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS System Init */
/* 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>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "service.h"
#include "common.h"
#include "session.h"


/* Session */
/* types */
struct _Session
{
	String * name;
	String * profile;
	Event * event;

	Config * config;
	Service ** services;
	size_t services_cnt;
};


/* prototypes */
/* private */
/* accessors */
static StringArray * _session_get_config_services(Session * session);
static String * _session_get_filename(Session * session);

/* useful */
static int _session_load(Session * session);


/* functions */
/* public */
/* session_new */
Session * session_new(char const * name, char const * profile, Event * event)
{
	Session * session;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\", %p)\n", __func__, name,
			profile, event);
#endif
	if(name == NULL || event == NULL)
	{
		error_set_code(1, "%s", strerror(EINVAL));
		return NULL;
	}
	if((session = object_new(sizeof(*session))) == NULL)
		return NULL;
	session->name = string_new(name);
	session->profile = (profile != NULL) ? string_new(profile) : NULL;
	session->event = event;
	session->config = config_new();
	session->services = NULL;
	session->services_cnt = 0;
	/* kickoff the session */
	/* FIXME should this be really fatal? annoying for Init */
	if(session_start(session) != 0)
	{
		session_delete(session);
		return NULL;
	}
	return session;
}


/* session_delete */
void session_delete(Session * session)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	string_delete(session->name);
	string_delete(session->profile);
	config_delete(session->config);
	object_delete(session);
}


/* AppInterface */
/* session_register */
int session_register(String const * interface, uint16_t port)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", %u)\n", __func__, interface, port);
#endif
	/* FIXME not implemented yet */
	return -1;
	/* authenticate client */
	/* check permissions */
	/* verify conflicts */
	/* register in the services list */
	/* return 0; */
}


/* useful */
/* session_start */
int session_start(Session * session)
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(_session_load(session) != 0)
		return -1;
	for(i = 0; i < session->services_cnt; i++)
		if(service_start(session->services[i]) != 0)
			/* we ignore the error because it gets monitored */
			error_print(PACKAGE);
	return 0;
}


/* session_stop */
int session_stop(Session * session)
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	/* FIXME should wait until the last service has stopped */
	/* FIXME again prone to race conditions */
	for(i = 0; i < session->services_cnt; i++)
		service_delete(session->services[i]);
	session->services_cnt = 0;
	return 0;
}


/* private */
/* session_get_config_services */
static StringArray * _session_get_config_services(Session * session)
{
	String const * profile;
	String const * services;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if((profile = session->profile) == NULL)
		profile = config_get(session->config, NULL, "profile");
	if((services = config_get(session->config, profile, "services"))
			== NULL)
		return NULL;
	return string_explode(services, ",");
}


/* session_get_filename */
static String * _session_get_filename(Session * session)
{
	char const * format = "%s/%s.%s";
	String * p;
	int size;

	if(session->name == NULL)
	{
		error_set_code(1, "%s", strerror(EINVAL));
		return NULL;
	}
	if((size = snprintf(NULL, 0, format, SESSIONDIR, session->name,
					SESSIONEXT)) <= 0)
	{
		error_set_code(1, "%s", strerror(EINVAL));
		return NULL;
	}
	if((p = object_new(++size)) == NULL) /* XXX string_new_format() ? */
		return NULL;
	snprintf(p, size, format, SESSIONDIR, session->name, SESSIONEXT);
	return p;
}


/* useful */
static void _load_service_foreach(void * value, void * data);
static int _load_service(Session * session, String const * service);

static int _session_load(Session * session)
{
	int ret;
	String * filename;
	StringArray * services;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(session->services_cnt != 0)
		return error_set_code(1, "%s", "Session is already started");
	/* lookup configuration file */
	if((filename = _session_get_filename(session)) == NULL)
		return -1;
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() filename=\"%s\"\n", __func__, filename);
#endif
	config_reset(session->config);
	ret = config_load(session->config, filename);
	string_delete(filename);
	if(ret != 0)
		return -1;
	/* parse the services list */
	if((services = _session_get_config_services(session)) == NULL)
		return -1;
	/* add the services */
	array_foreach(services, _load_service_foreach, session);
	array_foreach(services, (ArrayForeach)string_delete, NULL);
	array_delete(services);
	return 0;
}

static void _load_service_foreach(void * value, void * data)
{
	String * service = value;
	Session * session = data;

	if(_load_service(session, service) != 0)
		error_print(PACKAGE);
}

static int _load_service(Session * session, String const * service)
{
	Service ** p;
	size_t cnt = session->services_cnt;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, service);
#endif
	if((p = realloc(session->services, sizeof(*p) * (cnt + 1))) == NULL)
		return -error_set_code(1, "%s", strerror(errno));
	session->services = p;
	if((session->services[cnt] = service_new(service)) == NULL)
		return -1;
	session->services_cnt++;
	return 0;
}
