/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/


#include	"memory_debug.h"
#include	"xlerror.h"
#include	"xl.h"
#include	"version.h"
#include	"utils.h"


typedef struct session_sw {
	char *		proto;
	int		(*open)(int);
	void		(*close)(int);
	XL_SEXP * 	(*session)(
				XLISP_ENV * env,
				int id,
				URL * u,
				L_CHAR * _a_agent,
				L_CHAR * _a_login_mode,
				L_CHAR * _a_center_cmd,
				XL_SEXP * cmd,
				XL_FILE * f,
				int ln,
				int session_lock_enable,
				REMOTE_SESSION_OPT * );
	VERSION *	(*get_remote_version)(int id,URL * u,char * layer);
} SESSION_SW;

typedef struct session_remote_version {
	struct session_remote_version *	next;
	URL				url;
	VERSION *			v;
} SESSION_REMOTE_VERSION;


SESSION_REMOTE_VERSION *		rem_version;


typedef struct session_descriptor_list {
	struct session_descriptor_list *	next;
	SESSION_SW *				proto;
	int					id;
} SESSION_DESCRIPTOR_LIST;

typedef struct session_descriptor {
	struct session_descriptor *	next;
	int				id;
	int				open_opt;
	SESSION_DESCRIPTOR_LIST *	d_list;
} SESSION_DESCRIPTOR;

void
free_remote_version(SESSION_REMOTE_VERSION * v);
VERSION *
_search_remote_version(URL * u,char * layer);
VERSION *
_insert_remote_version(VERSION * v,URL * u,char * layer);

int open_session_xlp();
void close_session_xlp();
XL_SEXP * remote_session_xlp();
VERSION * get_remote_version_xlp(int id,URL * u,char * layer);

int open_session_http();
void close_session_http();
XL_SEXP * remote_session_http();

extern SEM	session_lock;
int	session_sw_id;
SESSION_DESCRIPTOR * sd_list;

SESSION_SW session_sw_table[] = {
	{	"xlp",
		open_session_xlp,
		close_session_xlp,
		remote_session_xlp,
		get_remote_version_xlp
	},
	{	"http",
		open_session_http,
		close_session_http,
		remote_session_http,
		0
	},
	{	0,
		0,
		0,
		0,
		0
	}
};


void
init_session()
{
	init_session_xlp();
	init_session_http();
}

int open_session(int open_opt)
{
int ret;
SESSION_DESCRIPTOR * sd;
	lock_task(session_lock);
	session_sw_id++;
	if ( session_sw_id <= 0 )
		session_sw_id = 1;
	ret = session_sw_id;
	sd = d_alloc(sizeof(*sd));
	memset(sd,0,sizeof(*sd));
	sd->id = ret;
	sd->open_opt = open_opt;
	sd->next = sd_list;
	sd_list = sd;
	unlock_task(session_lock,"open_session");
	return ret;
}

void
free_remote_version(SESSION_REMOTE_VERSION * v)
{
VERSION * v2;
	if ( v == 0 )
		return;
	free_url(&v->url);
	for ( ; v->v ; ) {
		v2 = v->v;
		free_version(v->v);
		v->v = v2;
	}
}

VERSION *
_search_remote_version(URL * u,char * layer)
{
VERSION * ret;
SESSION_REMOTE_VERSION * rv;
	for ( rv = rem_version ; rv ; rv = rv->next ) {
		if ( l_strcmp(rv->url.proto,u->proto) )
			continue;
		if ( l_strcmp(rv->url.server,u->server) )
			continue;
		if ( l_strcmp(rv->url.proto,u->proto) )
			continue;
		if ( l_strcmp(rv->url.agent,u->agent) )
			continue;
		for ( ret = rv->v ; ret ; ret = ret->next ) {
			if ( strcmp(ret->layer,layer) )
				continue;
			return ret;
		}
	}
	return 0;
}

VERSION *
_insert_remote_version(VERSION * v,URL * u,char * layer)
{
VERSION * ret;
SESSION_REMOTE_VERSION * rv;
	for ( rv = rem_version ; rv ; rv = rv->next ) {
		if ( l_strcmp(rv->url.proto,u->proto) )
			continue;
		if ( l_strcmp(rv->url.server,u->server) )
			continue;
		if ( l_strcmp(rv->url.proto,u->proto) )
			continue;
		if ( l_strcmp(rv->url.agent,u->agent) )
			continue;
		for ( ret = rv->v ; ret ; ret = ret->next ) {
			if ( strcmp(ret->layer,layer) )
				continue;
			return ret;
		}
		goto ins_ver;
	}
	rv = d_alloc(sizeof(*rv));
	memset(rv,0,sizeof(*rv));
	copy_url(&rv->url,u);
	rv->next = rem_version;
	rem_version = rv;
ins_ver:
	v->next = rv->v;
	rv->v = v;
	return v;
}

void
close_session(int id)
{
SESSION_DESCRIPTOR ** sdp, * sd;
SESSION_DESCRIPTOR_LIST * p, * p1;
	lock_task(session_lock);
	for ( sdp = &sd_list ; *sdp ; sdp = &(*sdp)->next )
		if ( (*sdp)->id == id ) {
			sd = *sdp;
			p = sd->d_list;
			*sdp = sd->next;
			d_f_ree(sd);
			goto ok;
		}
	unlock_task(session_lock,"open_session");
	return;
ok:
	unlock_task(session_lock,"open_session");
	for ( p1 = p ; p1 ; p1 = p1->next ) {
		(*p1->proto->close)(p1->id);
	}
	for ( ; p ; ) {
		p1 = p;
		p = p->next;
		d_f_ree(p1);
	}
}

XL_SEXP *
remote_session(
	XLISP_ENV * env,
	int id,
	URL * u,
	L_CHAR * _a_agent,
	L_CHAR * _a_login_mode,
	L_CHAR * _a_center_cmd,
	XL_SEXP * cmd,
	XL_FILE * f,
	int ln,
	int session_lock_enable,
	REMOTE_SESSION_OPT * opt)
{
SESSION_DESCRIPTOR * 	sd;
XL_SEXP * ret;
SESSION_DESCRIPTOR_LIST * sdl;
SESSION_SW * sw;
int _id;
	lock_task(session_lock);
	for ( sd = sd_list ; sd ; sd = sd->next )
		if ( sd->id == id )
			break;
	if ( sd == 0 ) {
		unlock_task(session_lock,"remote_session");
		ret = get_error(
			f,
			ln,
			XLE_PROTO_INV_PARAM,
			l_string(std_cm,"RemoteSession"),
			List(n_get_string("invalid session id(session)"),
				get_integer(id,0),
				-1));
		goto err;
	}
	for ( sdl = sd->d_list ; sdl ; sdl = sdl->next ) {
		if ( l_strcmp(l_string(std_cm,sdl->proto->proto),
				u->proto) == 0 )
			break;
	}
	if ( sdl == 0 ) {
		for ( sw = &session_sw_table[0];
				sw->proto;
				sw ++ )
			if ( l_strcmp(l_string(std_cm,sw->proto),
					u->proto) == 0 )
				goto ok;
		unlock_task(session_lock,"remote_session");
		ret = get_error(
			f,
			ln,
			XLE_PROTO_INV_PARAM,
			l_string(std_cm,"RemoteSession"),
			List(n_get_string("invalid session protocol"),
				get_string(u->proto),
				-1));
		goto err;
	ok:
		_id = (*sw->open)(sd->open_opt);
		if ( id < 0 ) {
			ret = get_error(
				f,
				ln,
				XLE_PROTO_INV_PARAM,
				l_string(std_cm,"RemoteSession"),
				List(n_get_string("cannot connect the protocol"),
					get_string(u->proto),
					-1));
			goto err;
		}
		sdl = d_alloc(sizeof(*sdl));
		memset(sdl,0,sizeof(*sdl));
		sdl->proto = sw;
		sdl->id = _id;
		sdl->next = sd->d_list;
		sd->d_list = sdl;
	}
	else {
		_id = sdl->id;
		sw = sdl->proto;
	}
	unlock_task(session_lock,"remote_session");
	ret = (*sw->session)(
			env,
			_id,
			u,
			_a_agent,
			_a_login_mode,
			_a_center_cmd,
			cmd,
			f,
			ln,
			session_lock_enable,
			opt);
err:
	return ret;
}


VERSION *
get_remote_version(
	int id,
	URL * u,
	char * layer)
{
SESSION_DESCRIPTOR * 	sd;
VERSION * ret;
SESSION_DESCRIPTOR_LIST * sdl;
VERSION * v;
SESSION_SW * sw;
int _id;
	ret = 0;

	lock_task(session_lock);
	ret = _search_remote_version(u,layer);
	if ( ret ) {
		unlock_task(session_lock,"remote_session");
		goto err;
	}
	for ( sd = sd_list ; sd ; sd = sd->next )
		if ( sd->id == id )
			break;
	if ( sd == 0 ) {
		unlock_task(session_lock,"remote_session");
		ret = 0;
		goto err;
	}
	for ( sdl = sd->d_list ; sdl ; sdl = sdl->next ) {
		if ( l_strcmp(l_string(std_cm,sdl->proto->proto),
				u->proto) == 0 )
			break;
	}
	if ( sdl == 0 ) {
		for ( sw = &session_sw_table[0];
				sw->proto;
				sw ++ )
			if ( l_strcmp(l_string(std_cm,sw->proto),
					u->proto) == 0 )
				goto ok;
		unlock_task(session_lock,"remote_session");
		ret = 0;
		goto err;
	ok:
		_id = (*sw->open)(sd->open_opt);
		if ( id < 0 ) {
			ret = 0;
			goto err;
		}
		sdl = d_alloc(sizeof(*sdl));
		memset(sdl,0,sizeof(*sdl));
		sdl->proto = sw;
		sdl->id = _id;
		sdl->next = sd->d_list;
		sd->d_list = sdl;
	}
	else {
		_id = sdl->id;
		sw = sdl->proto;
	}
	unlock_task(session_lock,"remote_session");

	if ( sw->get_remote_version ) {
		ret = (*sw->get_remote_version)(
				_id,
				u,
				layer);
		if ( ret == 0 )
			goto err;
	}
	else {
		ret = 0;
		goto err;
	}
	lock_task(session_lock);
	v = _insert_remote_version(ret,u,layer);
	if ( v != ret )
		free_version(ret);
	ret = v;
	unlock_task(session_lock,"remote_session");
err:
	return ret;
}

void
setup_remote_session_opt(REMOTE_SESSION_OPT * opt)
{
	opt->timeout_count = -1;
}

