/*
 * © 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 <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <sys/wait.h>
#include "mars.h"

#if mars_client_HAVE_RPC

#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, NULL)) < 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, (caddr_t)&s->soc,
                  (xdrproc)&readtcp, (xdrproc)&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,
                          (caddr_t)&id->s->soc, (xdrproc)&readtcp, (xdrproc)&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

#endif /* mars_client_HAVE_RPC */
