/*
 * © Copyright 1996-2012 ECMWF.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include "mars.h"
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#ifdef sgi
#include <bstring.h>
#endif

/* #include <netinet/tcp.h> */

#ifdef CRAY
typedef u_long xdr_t;
#else
typedef void *xdr_t;
#endif


static err send_any(svc *s,char *name,const request *r,request *info);

static void server_dead(svc *s)
{
	close(s->soc);
	s->soc = -1;
	marslog(LOG_EXIT,"Server %s port %d is dead",s->host,s->port);
}

err encode_request(const request *r,XDR *x)
{
	struct netblk blk;
	extern int _tcpdbg;
	/* _tcpdbg = 1;  */

	bzero(&blk,sizeof(blk));
	blk.req = (request*)r;
	x->x_op = XDR_ENCODE;

	marslog(LOG_DBUG,"encode_request : xdr_netblk %s %s",
	    r?r->name:"?",(r && r->next)?r->next->name:"?");

	if(!xdr_netblk(x,&blk))
	{
		/* marslog(LOG_WARN,"encode_request : xdr_netblk"); */
		return -2;
	}

	marslog(LOG_DBUG,"encode_request : xdrrec_endofrecord");

	if(!xdrrec_endofrecord(x,true))
	{
		/* marslog(LOG_WARN,"encode_request : xdrrec_endofrecord"); */
		return -2;
	}


	/* _tcpdbg = 0; */
	return 0;
}

request *decode_request(XDR *x)
{
	struct netblk blk;
	request *r;
	request *s;
	extern int _tcpdbg;
	/* _tcpdbg = 1;  */

	bzero(&blk,sizeof(blk));


	x->x_op = XDR_DECODE;

	marslog(LOG_DBUG,"decode_request : xdrrec_skiprecord");

	if(!xdrrec_skiprecord(x))
	{
		/* marslog(LOG_WARN,"decode_request : xdrrec_skiprecord"); */
		return (void*)-1;
	}

	marslog(LOG_DBUG,"decode_request : xdr_netblk");

	if(!xdr_netblk(x,&blk))
	{
		/* marslog(LOG_WARN,"decode_request : xdr_netblk"); */
		return (void*)-1;
	}

	r = blk.req;

	marslog(LOG_DBUG,"decode_request : --------> %s %s",
	    r?r->name:"?",(r && r->next)?r->next->name:"?");

	blk.req = NULL;
	xdr_free((xdrproc_t) xdr_netblk, (char*) &blk);

	/* _tcpdbg = 0; */

	s = r;
	while(s)
	{
		parameter *p = s->params;
		while(p)
		{
			p->count = 0;
			p = p->next;
		}
		s = s->next;
	}


	return r;
}


void destroy_service(svc *s)
{
	send_any(s,"EXIT",NULL,NULL);
	close(s->soc);
	xdr_destroy(&s->x);
	FREE(s);
}

static void register_service(svc *s)
{
	char    host[80];
	request *r = empty_request("REGISTER");

	gethostname(host,sizeof(host));

	set_value(r,"USER",   "%s", user(NULL));
	set_value(r,"NAME",   "%s", s->name);
	set_value(r,"HOST",   "%s", host);
	set_value(r,"PID",    "%d", getpid());

	svc_connect(s);
	if(encode_request(r,&s->x)<0) server_dead(s);
	free_all_requests(r);
}

err svc_connect(svc *s)
{

	if(s->soc>=0)
		return 0;

	if((s->soc = call_server(s->host,s->port,1))<0)
	{
		marslog(LOG_EROR,"Cannot connect to server");
		exit(8); /* The value 8 is used in the script */
	}

	if(fcntl(s->soc,F_SETFD,FD_CLOEXEC)<0)
		marslog(LOG_EROR|LOG_PERR,"fcntl");

	marslog(LOG_DBUG,"Connected to server");
	register_service(s);

	return 0;
}

static void _record(svcid *id,request *r,void *data)
{
	const char *p = get_value(r,"switch",0);
	id->s->recording = p && (strcmp(p,"on") == 0);
}

svc  *create_service(const char *name)
{
	char    buf[128];
	svc     *s = NEW_CLEAR(svc);
	char    *host = getenv("EVENT_HOST");
	char    *port = getenv("EVENT_PORT");

	s->host = host?host:"localhost";
	s->port = port?atoi(port):8000;

	if(name == NULL) {
		sprintf(buf,"%s@%d",progname(),getpid());
		name = buf;
	}

	s->soc   = -1;
	s->name  = strcache(name);

	xdrrec_create(&s->x,0,0,(mxdrparam)&s->soc,
	    (mxdrproc)readtcp,(mxdrproc)writetcp);

	svc_connect(s);


	return s;
}


static svcid *new_id(svc *s,request *r)
{
	svcid *id  = NEW_CLEAR(svcid);

	id->s     = s;
	id->r     = r;
	id->next  = s->id;
	s->id     = id;

	return id;

}

void set_svc_err(svcid *id,err e)
{
	set_value(id->r,"ERR_CODE","%d",e);
}

err get_svc_err(svcid *id)
{
	const char *e = get_value(id->r,"ERR_CODE",0);
	return e?atoi(e):0;
}

void set_svc_ref(svcid *id,long r)
{
	set_value(id->r,"USER_REF","%ld",r);
}

long get_svc_ref(svcid *id)
{
	const char *r = get_value(id->r,"USER_REF",0);
	return r?atol(r):0;
}

const char *get_svc_target(svcid *id)
{
	return get_value(id->r,"TARGET",0);
}

const char *get_svc_source(svcid *id)
{
	return get_value(id->r,"SOURCE",0);
}

const char *get_svc_msg(svcid *id,int n)
{
	char *q;

	if(count_values(id->r,"ERROR"))
		q = "ERROR";
	else
		q = "PROGRESS";
	return get_value(id->r,q,n);
}

void set_svc_msg(svcid *id,const char *fmt,...)
{
	va_list list;
	char buf[1024];
	va_start(list,fmt);
	vsprintf(buf, fmt, list);
	va_end(list);
	add_value(id->r,"ERROR","%s",buf);
}

static void free_id(svc *s,svcid *id)
{
	svcid *p = s->id;
	svcid *q = NULL;

	while(p)
	{
		if(p == id)
		{
			free_all_requests(p->r);
			if(q) q->next = p->next;
			else s->id = p->next;
			FREE(p);
			return;
		}
		q = p;
		p = p->next;
	}
}

/*****************************************************************************/
/* messages */
/*****************************************************************************/

static err send_any(svc *s,char *name,const request *r,request *info)
{
	request *t = empty_request(name);

	/* set_value(t,"SOURCE","%s",s->name); */

	if(info) reqcpy(t,info);
	t->next = (request*)r;

	svc_connect(s);
	if(encode_request(t,&s->x)<0) server_dead(s);

	t->next = NULL;

	free_all_requests(t);
	return 0;
}

err send_drop_info(svc *s,char *target,request *r,long ref)
{
	request *t = empty_request(NULL);
	err e;
	set_value(t,"USER_REF", "%ld", ref);
	if(target) set_value(t,"TARGET",   "%s", target);
	e = send_any(s,"DROP",r,t);
	free_all_requests(t);
	return e;
}

err send_message(svc *s,request *r)
{
	return send_any(s,"MESSAGE",r,NULL);
}

err send_progress(svcid *id,const char *msg,request *r)
{
	err e;

	if(msg) set_value(id->r,"PROGRESS", "%s", msg);

	/*
	Sylvie and Elisa change this line:e = send_any(id->s,"PROGRESS",NULL,id->r);
*/
	e = send_any(id->s,"PROGRESS",r,id->r);
	unset_value(id->r,"PROGRESS");

	return e;
}

static int children = 0;

static void death(int sig)
{
	int status;
#ifdef sun
	while(wait3(&status,WNOHANG,NULL) > 0 )
		children--;
#else
	wait(&status);
	children--;
#endif
	signal(SIGCHLD,death);
}

int fork_service(svcid *id)
{
	int f;
	char buf[1024];
#ifdef SIG_BLOCK
    sigset_t set;
#endif


	if(mars.nofork) return -1;

	if(mars.maxforks > 0 && children>=mars.maxforks)
		return -1;

	signal(SIGCHLD,death);

#if 0
	if(mars.maxforks > 0 && children>=mars.maxforks)
	{
		marslog(LOG_INFO,"To many requests for service %s, queuing....",
		    id->s->name);
		while(children>=mars.maxforks)
			pause();
	}
#endif

#ifdef SIG_BLOCK
            sigemptyset(&set);
            sigaddset(&set, SIGCHLD);
            sigprocmask(SIG_BLOCK, &set, NULL);
#else
            sighold(SIGCHLD);
#endif

	switch(f = fork())
	{
	case 0:
#ifdef SIG_BLOCK
        sigprocmask(SIG_UNBLOCK, &set, NULL);
#else
        sigrelse(SIGCHLD);
#endif
		signal(SIGCHLD,SIG_DFL);
		id->s->waitp = NULL;

		/* disconnect */

		close(id->s->soc);
		xdr_destroy(&id->s->x);
		xdrrec_create(&id->s->x,0,0,
		    (mxdrparam)&id->s->soc,(mxdrproc)readtcp,(mxdrproc)writetcp);
		id->s->soc = -1;

		/* rename */

		sprintf(buf,"%s@%d",id->s->name,getpid());
		strfree(id->s->name);
		id->s->name = strcache(buf);
		svc_connect(id->s);

		/* follow request */

		send_any(id->s,"FOLLOWUP",NULL,id->r);
		break;

	case -1:
		marslog(LOG_EROR|LOG_PERR,"fork");
		break;

	default:
		children++;
		free_id(id->s,id);
		break;
	}

#ifdef SIG_BLOCK
	sigprocmask(SIG_UNBLOCK, &set, NULL);
#else
	sigrelse(SIGCHLD);
#endif

	return f;
}

err send_reply(svcid *id,request *r)
{
	err e = 0;

	/* copy underscores ... */

	if(r && id->r && id->r->next)
	{
		parameter *s = id->r->next->params;
		while(s)
		{
			if(*s->name == '_')
			{
				request *x = get_subrequest(r,s->name,0);
				if(x == NULL)
				{
					if( (x = get_subrequest(id->r->next,s->name,0)) )
						set_subrequest(r,s->name,x);
					else if(get_value(r,s->name,0) == NULL)
					{
						value *v = s->values;
						while(v)
						{
							add_value(r,s->name,"%s",v->name);
							v=v->next;
						}
					}
				}
				free_all_requests(x);
			}
			s = s->next;
		}
	}

	e = send_any(id->s,"REPLY",r,id->r);

	free_id(id->s,id);
	return e;
}

err send_later(svcid *id)
{
	char buf[1024];
	sprintf(buf,"Service %s cannot handle this request now, queuing",
	    id->s->name);
	send_progress(id,buf,0);
	send_any(id->s,"LATER",id->r,0);
	free_id(id->s,id);
	return 0;
}

err send_number_reply(svcid *id,double d)
{
	err e;
	request *r = empty_request("NUMBER");
	set_value(r,"VALUE","%g",d);
	e = send_reply(id,r);
	free_all_requests(r);
	return e;
}

err send_string_reply(svcid *id,char *p)
{
	err e;
	request *r = empty_request("STRING");
	set_value(r,"VALUE","%s",p);
	e = send_reply(id,r);
	free_all_requests(r);
	return e;
}

err call_switchboard(svc *s,request *r)
{
	svc_connect(s);
	if(encode_request(r,&s->x)<0) server_dead(s);
	s->replies++;
	return 0;
}

err call_service(svc *s,const char *target,const request *r,long ref)
{
	request *t = empty_request(NULL);
	err e;

	set_value(t,"USER_REF", "%ld", ref);

	if(s->waitp != NULL)
		set_value(t,"WAITMODE", "1");

	if(target) set_value(t,"TARGET",   "%s", target);

	e = send_any(s,"SERVICE",r,t);
	s->replies++;

	free_all_requests(t);

	return e;
}

static err _e = 0;

static void waitp(svcid *id,request *r,void *data)
{
	id->s->r = clone_all_requests(r);
	id->s->waitp = NULL;
	_e = get_svc_err(id);
}

request *wait_service(svc *s,char *target,request *r,err *e)
{
	s->waitp = waitp;

	_e = 0;
	call_service(s,target,r,0);
	while(service_sync(s) && (s->waitp != NULL) )
		;

	*e = _e;

	s->waitp = NULL;
	r = s->r;
	s->r = NULL;
	return r;

}

err call_function(svc *s,const char *target,const request *r,long ref)
{
	return call_service(s,target,r,ref);
}

/*****************************************************************************/
/* Call backs                                                                */
/*****************************************************************************/

static void add_callback(svcprocs **s,const char *name,svcproc p,
request *r,void *data)
{
	svcprocs *q = NEW_CLEAR(svcprocs);
	q->name  = strcache(name);
	q->proc  = p;
	q->next  = *s;
	q->args  = r;
	q->data  = data;
	*s 		 = q;
}

static void want(svc *s,const char *name,const char *kind)
{
	/* tell event which messages we want ... */
	request *r = empty_request(NULL);
	if(name) set_value(r,"NAME","%s",name);
	set_value(r,"TYPE","%s",kind);
	send_any(s,"WANT",NULL,r);
	free_all_requests(r);
}

void keep_alive(svc *s,int alive)
{
	request *r = empty_request(NULL);
	set_value(r,"ALIVE","%d",alive);
	send_any(s,"ALIVE",NULL,r);
	free_all_requests(r);
}

void stop_all(svc *s,const char *p,int code)
{
	request *r = empty_request(NULL);
	set_value(r,"STOP","%d",code);
	if(p) set_value(r,"INFO","%s",p);
	send_any(s,"STOP",NULL,r);
	free_all_requests(r);
}

void set_maximum(svc *s,int max)
{
	request *r = empty_request(NULL);
	set_value(r,"MAXIMUM","%d",max);
	send_any(s,"MAXIMUM",NULL,r);
	free_all_requests(r);
}

void exit_timeout(svc *s,int timeout)
{
	request *r = empty_request(NULL);
	set_value(r,"TIMEOUT","%d",timeout);
	send_any(s,"TIMEOUT",NULL,r);
	free_all_requests(r);
}

void add_progress_callback(svc *s,const char *name,svcproc p,void *data)
{
	want(s,name,"PROGRESS");
	add_callback(&s->prog,name,p,NULL,data);
}

void add_reply_callback(svc *s,const char *name,svcproc p,void *data)
{
	add_callback(&s->repl,name,p,NULL,data);
}

void add_drop_callback(svc *s,const char *name,svcproc p,void *data)
{
	add_callback(&s->drop,name,p,NULL,data);
}

void add_message_callback(svc *s,const char *name,svcproc p,void *data)
{
	want(s,name,"MESSAGE");
	add_callback(&s->mess,name,p,NULL,data);
}

static void _dictionary(svcid *id,request *r,void *data)
{
	svcprocs *p = id->s->fncs;
	request  *u = NULL;

	while(p)
	{
		request *r = empty_request(p->name);
		reqcpy(r,p->args);
		r->next = u;
		u = r;
		p = p->next;
	}
	send_reply(id,u);
	free_all_requests(u);
}

void add_function_callback(svc *s,const char *name,svcproc p,
const char   *cmt,argdef *args, void *data)
{
	int i = 0;
	request *r = empty_request(name);

	if(s->fncs == NULL)
		add_service_callback(s,"_dictionary",_dictionary,NULL);

	add_value(r,"_reply","%d",tany);
	if(cmt) add_value(r,"_info", "%s",cmt);

	while(args[i].name)
	{
		if(args[i].def)
			set_value(r,args[i].name,"%s",args[i].def);
		add_value(r,"_types","%d",args[i].kind);
		i++;
	}

	add_callback(&s->fncs,name,p,r,data);
	add_service_callback(s,name,p,data);
}

void add_input_callback(svc *s,FILE *f,inputproc p,void *data)
{
	inputprocs *q = NEW_CLEAR(inputprocs);
	q->proc  = p;
	q->next  = s->input;
	q->data  = data;
	q->f     = f;
	s->input  = q;
}

void add_service_callback(svc *s,const char *name,svcproc p,void *data)
{
	add_callback(&s->serv,name,p,NULL,data);
}

/**************************************************************************/
/* Dispatching */
/**************************************************************************/

static boolean nextevent(svc *s,struct timeval *timeout)
{
	fd_set   fds;
	inputprocs *p,*q;


	for(;;)
	{
		FD_ZERO(&fds);
		FD_SET(s->soc,&fds);

		p = s->input;
		while(p)
		{
			FD_SET(fileno(p->f),&fds);
			p = p->next;
		}

		switch(select(FD_SETSIZE,&fds,NULL,NULL,timeout))
		{
		case -1:
			if(errno != EINTR)
				marslog(LOG_EXIT|LOG_PERR,"select");
			break;

			/* return timeout */
		case 0:
			return false;
			/*NOTREACHED*/
			break;

		default:
			/* something came */
			if(FD_ISSET(s->soc,&fds))
				return true;

			/* dispatch input */
			p = s->input;
			q = NULL;
			while(p)
			{
				if(FD_ISSET(fileno(p->f),&fds))
					if(!p->proc(p->f,p->data))
					{
						if(q) q->next = p->next;
						else s->input = p->next;
						FREE(p);
						break;
					}
				q = p;
				p = p->next;
			}

			break;
		}
	}
}

boolean service_ready(svc *s)
{
	struct timeval timeout = {
		0 , 1,				};
	return nextevent(s,&timeout);
}

static void terminate(const char *msg)
{
	marslog(LOG_EXIT,"Connection terminated: %s",msg);
}


err re_dispatch(svcid* id,request  *r)
{
	svcprocs *p = id->s->serv;
	boolean  ok = false;

	if(!r) return 0;

	while(p)
	{
		if( *r->name != '_' && p->name == r->name)
		{

			request* old = id->r->next;
			id->r->next = clone_all_requests(r);
			free_all_requests(old);
			p->proc(id,r,p->data);

			ok = true;
			break;
		}
		p = p->next;
	}

	/* services keep the id until send reply */

	if(!ok)
	{
		set_svc_err(id,-63);
		set_svc_msg(id,"Service %s has no handler for request %s",
		    id->s->name,r->name);
		send_reply(id,NULL);
	}

	return 0;
}

err process_service(svc *s)
{
	struct timeval timeout;

	if(s->timeout)
	{
		timeout.tv_sec  = s->timeout;
		timeout.tv_usec = 0;
	}


	if(!nextevent(s,s->timeout?&timeout:NULL))
		return 1;

	do 
	{
		request  *r = NULL;
		svcprocs *p = NULL;
		svcid    *id;
		boolean  serve;
		boolean  ok = false;
		int     try = 0;

		r = decode_request(&s->x);

		while ( r == (request*)(void*)-1 )
		{
			alarm(1);
			r = decode_request(&s->x);
			alarm(0);

			if(try++>3)
			{
				server_dead(s);
				return 1;
			}
		}

		if(r == NULL) return 0;

		if(mars.debug)
		{
			marslog(LOG_DBUG,"get service:");
			print_all_requests(r);
		}

		id = new_id(s,r);

		if( (serve = (strcmp(r->name,"SERVICE")==0)) ) p = s->serv;
		else if(strcmp(r->name,"REPLY")==0)   {
			p = s->repl;
			s->replies--;
		}
		else if(strcmp(r->name,"REPLY")==0)    p = s->repl;
		else if(strcmp(r->name,"DROP")==0)     p = s->drop;
		else if(strcmp(r->name,"MESSAGE")==0)  p = s->mess;
		else if(strcmp(r->name,"PROGRESS")==0) p = s->prog;
		else if(strcmp(r->name,"EXIT")==0) terminate(get_value(r,"MESSAGE",0));
		else {
			marslog(LOG_WARN,"Unknow request received:");
			print_all_requests(r);
			continue;
		}

		if(s->waitp != NULL &&
		    strcmp(r->name,"REPLY")==0  && 
		    get_value(r,"WAITMODE",0) != NULL)
		{
			s->waitp(id,r->next,NULL);
			ok = true;
		}
		else {
			/* Try named ... */
			svcprocs *x = p;
			while(p)
			{
				if(p->name != NULL && r->next != NULL && p->name == r->next->name)
				{
					p->proc(id,r->next,p->data);
					ok = true;
					if(serve) break;
				}
				p = p->next;
			}

			/* Try NULL */
			if(!ok)
			{
				p = x;
				while(p)
				{
				    if( 
						(p->name == NULL && 
							(r->next != NULL && r->next->name != NULL && 
						(*r->next->name != '_' ))) ||
						(r->next == NULL && p->name == NULL))
					{
						 p->proc(id,r->next,p->data);
						 ok = true;
						 if(serve) break;
					}
					p = p->next;
				}
			}

		}

		/* services keep the id until send reply */

		if(serve)
		{
			if(!ok)
			{
				set_svc_err(id,-63);
				set_svc_msg(id,"Service %s has no handler for request %s",
				    s->name,
				    r->next?r->next->name:"(no name)");
				send_reply(id,NULL);
			}
		}
		else
			free_id(s,id);

	}while(!xdrrec_eof(&s->x));

	return 0;

}

int service_sync(svc *s)
{
	if(s->replies)
	{
		svc_connect(s);
		process_service(s);
	}
	return s->replies;
}

void service_run(svc *s)
{
	for(;;) {
		svc_connect(s);
		if(process_service(s))
			break; /* Timeout */
	}
}
/*****************************************************************************/
/* Utilities                                                                 */
/*****************************************************************************/

request *pool_fetch(svc *s,const char *name,const char *clss)
{
	static request *u = NULL;
	request *r;
	err e;
	if(!name) return NULL;
	if(u == NULL) u = empty_request("FETCH");
	set_value(u,"NAME","%s",name);
	if(clss)
		set_value(u,"CLASS","%s",clss);
	else
		unset_value(u,"CLASS");
	r =  wait_service(s,"pool",u,&e);
	if(e) {
		free_all_requests(r);
		return NULL;
	}
	return r;
}

void pool_store(svc *s,const char *name,const char *clss,const request *r)
{
	err e;
	static request *u = NULL;
	if (!name) return;
	if(u == NULL) u = empty_request("STORE");
	set_value(u,"NAME", "%s",name);
	if(clss)
		set_value(u,"CLASS","%s",clss);
	else
		unset_value(u,"CLASS");
	u->next = (request*)r;
	wait_service(s,"pool",u,&e);
}

void pool_link(svc *s,const char *name1,const char *name2)
{
	err e;
	static request *u = NULL;

	if(!name1 || !name2) return;

	if(u == NULL) u = empty_request("LINK");
	set_value(u,"NAME1","%s",name1);
	set_value(u,"NAME2","%s",name2);
	wait_service(s,"pool",u,&e);
}

void pool_link_objects(svc *s,request *r)
{
	const char *name = get_value(r,"_NAME",0);
	while(r)
	{
		parameter *p = r->params;
		while(p)
		{
			request *t = p->subrequest;
			while(t)
			{
				const char *other = get_value(t,"_NAME",0);
				if(other) pool_link(s,name,other);
				pool_link_objects(s,t);
				t = t->next;
			}

			p = p->next;
		}
		r = r->next;
	}
}


static void _birth(svcid *id,request *r,void *data)
{
	/* tell newcomers ... */
	if(id->s->recording) recording(id->s,true);
}

void recording(svc *s,boolean on)
{
	static boolean first = true;
	request *u = empty_request("_record");
	set_value(u,"switch",on?"on":"off");
	send_message(s,u);
	free_all_requests(u);

	s->recording = on;

	if(first)
	{
		first = true;
		/* tell future modules about recording */
		add_message_callback(s,"BIRTH",_birth,NULL);
	}
}

void support_recording(svc *s)
{
	add_message_callback(s,"_record",_record,NULL);
}

static int tempvars = 0;

void record_request(svc *s,char *name,request *r)
{
	if(!s->recording) return;
	record_line(s,"%s = some_request_not_yet_implemeted()",name);
}

void record_function(svc *s,const char *name,argdef *args,va_list list)
{
	int     n,i;
	char   *p;
	double  d;
	request *r;
	char line[1024],buf[1024];

	if(!s->recording) return;

	sprintf(line,"%s(",name);

	i = 0;
	while(args[i].name)
	{
		switch(args[i].kind)
		{
		case tinteger:
			n = va_arg(list,int);
			sprintf(buf,"%d",n);
			break;

		case tnumber:
			d = va_arg(list,double);
			sprintf(buf,"%g",d);
			break;

		case tstring:
			p = va_arg(list,char*);
			sprintf(buf,"\"%s\"",p);
			break;

		case trequest:
			r = va_arg(list,request*);
			sprintf(buf,"_%s_temp_var_%d_",s->name,tempvars++);
			record_request(s,buf,r);
			break;

		default:
			marslog(LOG_WARN,"record_function : unsupported type");
			break;
		}

		strcat(line,buf);

		if(args[i+1].name) strcat(line,",");

		i++;
	}
	strcat(line,")");

	record_line(s,"%s",line);
}

void record_line(svc *s,const char *fmt,...)
{
	if(s->recording)
	{
		va_list list;
		char buf[1024];
		static request *u = NULL;
		if(u == NULL) u = empty_request("RECORD");

		va_start(list,fmt);
		vsprintf(buf, fmt, list);
		va_end(list);

		set_value(u,"TEXT","%s",buf);
		send_message(s,u);
	}

}

void show_help_page(svc *s,const char *page,char *topic)
{
	request *u = empty_request("HELP");
	if(page)  set_value(u,"PAGE","%s",page);
	if(topic) set_value(u,"TOPIC","%s",topic);
	call_service(s,"help",u,0);
	free_all_requests(u);
}

void show_help_text(svc *s,const char *page,const char *topic,const char *fmt,...)
{
	va_list list;
	char buf[1024];

	request *u = empty_request("HELP");

	va_start(list,fmt);
	vsprintf(buf, fmt, list);
	va_end(list);

	if(page)  set_value(u,"PAGE","%s",page);
	if(topic) set_value(u,"TOPIC","%s",topic);

	set_value(u,"TEXT","%s",buf);

	call_service(s,"help",u,0);
	free_all_requests(u);
}

void show_help_file(svc *s,const char *page,const char *topic,const char *file)
{
	request *u = empty_request("HELP");
	if(page)  set_value(u,"PAGE","%s",page);
	if(topic) set_value(u,"TOPIC","%s",topic);
	set_value(u,"FILE","%s",file);
	call_service(s,"help",u,0);
	free_all_requests(u);
}

#ifdef METVIEW
request *get_preferences(svc *s,const char *name)
{
	char buf[1024];
	sprintf(buf,"%s/Metview/Preferences/%s",
	    getenv("METVIEW_USER_DIRECTORY"),name?name:"General");
	return read_request_file(buf);
}

void set_preferences(svc *s,request *r,const char *name)
{
	char buf[1024];
	FILE *f;
	sprintf(buf,"%s/Metview/Preferences/%s",
	    getenv("METVIEW_USER_DIRECTORY"),name?name:"General");
	f = fopen(buf,"w");
	if(!f) {
		marslog(LOG_EROR|LOG_PERR,"Cannot open %s",buf);
		return;
	}
	save_all_requests(f,r);
	fclose(f);

	sprintf(buf,"/Metview/Preferences/%s",name?name:"General");
	set_value(r,"_NAME","%s",name);
	send_message(s,r);
}

#endif

