/* socket.c -- routines for controlling transcode over a socket
 * Written by Andrew Church <achurch@achurch.org>
 *
 * This file is part of transcode, a video stream processing tool.
 * transcode is free software, distributable under the terms of the GNU
 * General Public License (version 2 or later).  See the file COPYING
 * for details.
 */

#include "transcode.h"
#include "encoder.h"
#include "filter.h"
#include "socket.h"
#include "libtc/libtc.h"

#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

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

/* Pathname for listener socket */
static char socket_path[PATH_MAX+1] = "";
/* Socket descriptors for listener socket and client socket */
static int server_sock = -1;
static int client_sock = -1;

/* For communicating with "pv" module (FIXME: should go away) */
pthread_mutex_t tc_socket_msg_lock = PTHREAD_MUTEX_INITIALIZER;
enum tc_socket_msg_cmd_enum tc_socket_msg_cmd;
int tc_socket_msg_arg = 0;

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

/**
 * sendall: Send data to a client_sock, handling partial writes transparently.
 *
 * Parameters:
 *      sock: Socket descriptor.
 *       buf: Data to write.
 *     count: Number of bytes to send.
 * Return value:
 *     Total number of bytes sent; -1 indicates that the send failed.
 */

static int sendall(int sock, const void *buf, size_t count)
{
    int total_sent = 0;

    if (sock < 0 || !buf || count < 0) {
        tc_log_warn(__FILE__, "sendall(): invalid parameters!");
        errno = EINVAL;
        return -1;
    }
    while (count > 0) {
        int retval = send(sock, buf, count, 0);
        if (retval <= 0) {
            tc_log_warn(__FILE__, "sendall(): socket write failed (%s)",
                        retval<0 ? strerror(errno) : "Connection closed");
            if (total_sent == 0)
                return -1;  /* Nothing sent yet, abort with error return */
            break;
        }
        total_sent += retval;
        buf = (int8_t *)buf + retval;
        count -= retval;
    }
    return total_sent;
}

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

/**
 * sendstr: Send a string to a client_sock, handling partial writes transparently.
 *
 * Parameters:
 *      sock: Socket descriptor.
 *       str: String to write.
 * Return value:
 *     Total number of bytes sent; -1 indicates that the send failed.
 */

static int sendstr(int sock, const char *str)
{
    return sendall(sock, str, strlen(str));
}

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

/**
 * dump_vob:  Send the contents of the global `vob' structure over the
 * socket in a "parameter=value" format, one field per line.
 *
 * Parameters:
 *     sock: Socket descriptor to send data to.
 * Return value:
 *     None.
 */

static void dump_vob(int sock)
{
    vob_t *vob = tc_get_vob();
    char buf[1000];
    int n;

#define SEND(field,fmt) \
    n = tc_snprintf(buf, sizeof(buf), "%s=" fmt "\n", #field, vob->field); \
    if (n > 0) \
        sendall(sock, buf, n);

    /* Generated via find-and-replace from vob_t definition in transcode.h */
    SEND(vmod_probed, "%s");
    SEND(amod_probed, "%s");
    SEND(vmod_probed_xml, "%s");
    SEND(amod_probed_xml, "%s");
    SEND(verbose, "%d");
    SEND(video_in_file, "%s");
    SEND(audio_in_file, "%s");
    SEND(nav_seek_file, "%s");
    SEND(has_audio, "%d");
    SEND(has_audio_track, "%d");
    SEND(has_video, "%d");
    SEND(lang_code, "%d");
    SEND(a_track, "%d");
    SEND(v_track, "%d");
    SEND(s_track, "%d");
    SEND(sync, "%d");
    SEND(sync_ms, "%d");
    SEND(dvd_title, "%d");
    SEND(dvd_chapter1, "%d");
    SEND(dvd_chapter2, "%d");
    SEND(dvd_max_chapters, "%d");
    SEND(dvd_angle, "%d");
    SEND(ps_unit, "%d");
    SEND(ps_seq1, "%d");
    SEND(ps_seq2, "%d");
    SEND(ts_pid1, "%d");
    SEND(ts_pid2, "%d");
    SEND(vob_offset, "%d");
    SEND(vob_chunk, "%d");
    SEND(vob_chunk_num1, "%d");
    SEND(vob_chunk_num2, "%d");
    SEND(vob_chunk_max, "%d");
    SEND(vob_percentage, "%d");
    SEND(vob_psu_num1, "%d");
    SEND(vob_psu_num2, "%d");
    SEND(vob_info_file, "%s");
    SEND(pts_start, "%f");
    SEND(psu_offset, "%f");
    SEND(demuxer, "%d");
    SEND(v_format_flag, "%ld");
    SEND(v_codec_flag, "%ld");
    SEND(a_format_flag, "%ld");
    SEND(a_codec_flag, "%ld");
    SEND(quality, "%d");
    SEND(a_stream_bitrate, "%d");
    SEND(a_chan, "%d");
    SEND(a_bits, "%d");
    SEND(a_rate, "%d");
    SEND(a_padrate, "%d");
    SEND(im_a_size, "%d");
    SEND(ex_a_size, "%d");
    SEND(im_a_codec, "%d");
    SEND(a_leap_frame, "%d");
    SEND(a_leap_bytes, "%d");
    SEND(a_vbr, "%d");
    SEND(a52_mode, "%d");
    SEND(dm_bits, "%d");
    SEND(dm_chan, "%d");
    SEND(v_stream_bitrate, "%d");
    SEND(fps, "%f");
    SEND(im_frc, "%d");
    SEND(ex_fps, "%f");
    SEND(ex_frc, "%d");
    SEND(hard_fps_flag, "%d");
    SEND(pulldown, "%d");
    SEND(im_v_height, "%d");
    SEND(im_v_width, "%d");
    SEND(im_v_size, "%d");
    SEND(im_asr, "%d");
    SEND(im_par, "%d");
    SEND(im_par_width, "%d");
    SEND(im_par_height, "%d");
    SEND(ex_asr, "%d");
    SEND(ex_par, "%d");
    SEND(ex_par_width, "%d");
    SEND(ex_par_height, "%d");
    SEND(attributes, "%d");
    SEND(im_v_codec, "%d");
    SEND(encode_fields, "%d");
    SEND(dv_yuy2_mode, "%d");
    SEND(volume, "%f");
    SEND(ac3_gain[0], "%f");
    SEND(ac3_gain[1], "%f");
    SEND(ac3_gain[2], "%f");
    SEND(clip_count, "%d");
    SEND(ex_v_width, "%d");
    SEND(ex_v_height, "%d");
    SEND(ex_v_size, "%d");
    SEND(reduce_h, "%d");
    SEND(reduce_w, "%d");
    SEND(resize1_mult, "%d");
    SEND(vert_resize1, "%d");
    SEND(hori_resize1, "%d");
    SEND(resize2_mult, "%d");
    SEND(vert_resize2, "%d");
    SEND(hori_resize2, "%d");
    SEND(zoom_width, "%d");
    SEND(zoom_height, "%d");
    SEND(zoom_interlaced, "%d");
    SEND(zoom_filter, "%d");
    SEND(antialias, "%d");
    SEND(deinterlace, "%d");
    SEND(decolor, "%d");
    SEND(aa_weight, "%f");
    SEND(aa_bias, "%f");
    SEND(gamma, "%f");
    SEND(ex_clip_top, "%d");
    SEND(ex_clip_bottom, "%d");
    SEND(ex_clip_left, "%d");
    SEND(ex_clip_right, "%d");
    SEND(im_clip_top, "%d");
    SEND(im_clip_bottom, "%d");
    SEND(im_clip_left, "%d");
    SEND(im_clip_right, "%d");
    SEND(post_ex_clip_top, "%d");
    SEND(post_ex_clip_bottom, "%d");
    SEND(post_ex_clip_left, "%d");
    SEND(post_ex_clip_right, "%d");
    SEND(pre_im_clip_top, "%d");
    SEND(pre_im_clip_bottom, "%d");
    SEND(pre_im_clip_left, "%d");
    SEND(pre_im_clip_right, "%d");
    SEND(video_out_file, "%s");
    SEND(audio_out_file, "%s");
    SEND(avifile_in, "%p");  // not sure if there's any point sending these...
    SEND(avifile_out, "%p");
    SEND(avi_comment_fd, "%d");
    SEND(audio_file_flag, "%d");
    SEND(divxbitrate, "%d");
    SEND(divxkeyframes, "%d");
    SEND(divxquality, "%d");
    SEND(divxcrispness, "%d");
    SEND(divxmultipass, "%d");
    SEND(video_max_bitrate, "%d");
    SEND(divxlogfile, "%s");
    SEND(min_quantizer, "%d");
    SEND(max_quantizer, "%d");
    SEND(rc_period, "%d");
    SEND(rc_reaction_period, "%d");
    SEND(rc_reaction_ratio, "%d");
    SEND(divx5_vbv_prof, "%d");
    SEND(divx5_vbv_bitrate, "%d");
    SEND(divx5_vbv_size, "%d");
    SEND(divx5_vbv_occupancy, "%d");
    SEND(mp3bitrate, "%d");
    SEND(mp3frequency, "%d");
    SEND(mp3quality, "%f");
    SEND(mp3mode, "%d");
    SEND(bitreservoir, "%d");
    SEND(lame_preset, "%s");
    SEND(audiologfile, "%s");
    SEND(ex_a_codec, "%d");
    SEND(ex_v_codec, "%d");
    SEND(ex_v_fcc, "%s");
    SEND(ex_a_fcc, "%s");
    SEND(ex_profile_name, "%s");
    SEND(pass_flag, "%d");
    SEND(encoder_flush, "%d");
    SEND(mod_path, "%s");
    SEND(ttime, "%p");  // format this as a -c style string?
    SEND(frame_interval, "%u");
    SEND(im_v_string, "%s");
    SEND(im_a_string, "%s");
    SEND(ex_v_string, "%s");
    SEND(ex_a_string, "%s");
    SEND(ex_m_string, "%s");
    SEND(m2v_requant, "%f");
    SEND(mpeg_profile, "%d");
    SEND(export_attributes, "%u");

#undef SEND
}

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

/* Socket actions. */

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

/**
 * handle_config():  Process a "config" command received on the socket.
 *
 * Parameters:
 *     params: Command parameters.
 * Return value:
 *     Nonzero on success, zero on failure.
 */

static int handle_config(char *params)
{
    char *filter_name, *filter_params;
    int filter_id;

    filter_name = params;
    filter_params = filter_name + strcspn(filter_name, " \t");
    *filter_params++ = 0;
    filter_params += strspn(filter_params, " \t");
    if (!*filter_name || !*filter_params)
        return 0;

    filter_id = tc_filter_find(filter_name);
    if (!filter_id)
        return 0;
    else
	return tc_filter_configure(filter_id, filter_params) == 0;
}

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

/**
 * handle_disable():  Process a "disable" command received on the socket.
 *
 * Parameters:
 *     params: Command parameters.
 * Return value:
 *     Nonzero on success, zero on failure.
 */

static int handle_disable(char *params)
{
    int filter_id;

    filter_id = tc_filter_find(params);
    if (!filter_id)
        return 0;
    else
        return tc_filter_disable(filter_id);
}

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

/**
 * handle_enable():  Process an "enable" command received on the socket.
 *
 * Parameters:
 *     params: Command parameters.
 * Return value:
 *     Nonzero on success, zero on failure.
 */

static int handle_enable(char *params)
{
    int filter_id;

    filter_id = tc_filter_find(params);
    if (!filter_id)
        return 0;
    else
        return tc_filter_enable(filter_id);
}

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

/**
 * handle_help():  Process a "help" command received on the socket.
 *
 * Parameters:
 *     params: Command parameters.
 * Return value:
 *     Nonzero on success, zero on failure.
 */

static int handle_help(char *params)
{
    sendstr(client_sock,
            "load <filter> <initial string>\n"
            "unload <filter>\n"
            "enable <filter>\n"
            "disable <filter>\n"
            "config <filter> <string>\n"
            "parameters <filter>\n"
            "list [ load | enable | disable ]\n"
            "dump\n"
            "progress\n"
            "pause\n"
            "preview <command>\n"
            "  [ draw | undo | pause | fastfw |\n"
            "    slowfw | slowbw | rotate |\n"
            "    rotate | display | slower |\n"
            "    faster | toggle | grab ]\n"
            "help\n"
            "version\n"
            "quit\n"
    );
    return 1;
}

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

/**
 * handle_list():  Process a "list" command received on the socket.
 *
 * Parameters:
 *     params: Command parameters.
 * Return value:
 *     Nonzero on success, zero on failure.
 */

static int handle_list(char *params)
{
    const char *list = NULL;

    if (strncasecmp(params, "load", 2) == 0)
        list = tc_filter_list(TC_FILTER_LIST_LOADED);
    else if (strncasecmp(params, "enable", 2) == 0)
        list = tc_filter_list(TC_FILTER_LIST_ENABLED);
    else if (strncasecmp(params, "disable", 2) == 0)
        list = tc_filter_list(TC_FILTER_LIST_DISABLED);

    if (list) {
        sendstr(client_sock, list);
        return 1;
    } else {
        return 0;
    }
}

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

/**
 * handle_load():  Process a "load" command received on the socket.
 *
 * Parameters:
 *     params: Command parameters.
 * Return value:
 *     Nonzero on success, zero on failure.
 */

static int handle_load(char *params)
{
    char *name, *options;

    name = params;
    options = strchr(params, ' ');
    if (options) {
        *options++ = 0;
        options += strspn(options, " \t");
    }
    return tc_filter_add(name, options);
}

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

/**
 * handle_parameter():  Process a "parameter" command received on the
 * socket.
 *
 * Parameters:
 *     params: Command parameters.
 * Return value:
 *     Nonzero on success, zero on failure.
 */

static int handle_parameter(char *params)
{
    int filter_id;
    const char *s;

    filter_id = tc_filter_find(params);
    if (!filter_id)
        return 0;
    s = tc_filter_get_conf(filter_id, NULL);
    if (!s)
        return 0;
    sendstr(client_sock, s);
    return 1;
}

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

/**
 * handle_preview():  Process a "preview" command received on the socket.
 *
 * Parameters:
 *     params: Command parameters.
 * Return value:
 *     Nonzero on success, zero on failure.
 */

static int handle_preview(char *params)
{
    int filter_id, cmd, arg;
    char *cmdstr, *argstr;

    /* Check that the preview filter is loaded, and load it if not */
    filter_id = tc_filter_find("pv");
    if (!filter_id) {
        filter_id = tc_filter_add("pv", "cache=20");
        if (!filter_id)
            return 0;
    }

    /* Parse out preview command name and (optional) argument */
    cmdstr = strtok(params, " \t");
    if (cmdstr)  // there are strtok() implementations that crash without this!
        argstr = strtok(NULL, " \t");
    else
        argstr = NULL;
    if (argstr)
        arg = atoi(argstr);
    else
        arg = 0;

    if        (!strncasecmp(cmdstr, "draw",    2)) {
	cmd = TC_SOCK_PV_DRAW;
    } else if (!strncasecmp(cmdstr, "pause",  2)) {
	cmd = TC_SOCK_PV_PAUSE;
    } else if (!strncasecmp(cmdstr, "undo", 2)) {
	cmd = TC_SOCK_PV_UNDO;
    } else if (!strncasecmp(cmdstr, "fastfw", 6)) {
	cmd = TC_SOCK_PV_FAST_FW;
    } else if (!strncasecmp(cmdstr, "fastbw", 6)) {
	cmd = TC_SOCK_PV_FAST_BW;
    } else if (!strncasecmp(cmdstr, "slowfw", 6)) {
	cmd = TC_SOCK_PV_SLOW_FW;
    } else if (!strncasecmp(cmdstr, "slowbw", 6)) {
	cmd = TC_SOCK_PV_SLOW_BW;
    } else if (!strncasecmp(cmdstr, "toggle", 6)) {
	cmd = TC_SOCK_PV_TOGGLE;
    } else if (!strncasecmp(cmdstr, "slower", 6)) {
	cmd = TC_SOCK_PV_SLOWER;
    } else if (!strncasecmp(cmdstr, "faster", 6)) {
	cmd = TC_SOCK_PV_FASTER;
    } else if (!strncasecmp(cmdstr, "rotate", 6)) {
	cmd = TC_SOCK_PV_ROTATE;
    } else if (!strncasecmp(cmdstr, "display", 6)) {
	cmd = TC_SOCK_PV_DISPLAY;
    } else if (!strncasecmp(cmdstr, "grab", 4)) {
	cmd = TC_SOCK_PV_SAVE_JPG;
    } else {
	return 0;
    }

    pthread_mutex_lock(&tc_socket_msg_lock);
    tc_socket_msg_cmd = cmd;
    tc_socket_msg_arg = arg;
    pthread_mutex_unlock(&tc_socket_msg_lock);

    return 1;
}

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

/**
 * handle:  Handle a single message from a socket.
 *
 * Parameters:
 *     buf: Line read from socket.
 * Return value:
 *     Zero if the socket is to be closed, nonzero otherwise.
 */

static int handle(char *buf)
{
    char *cmd, *params;
    int len, retval;

    len = strlen(buf);
    if (len > 0 && buf[len-1] == '\n')
        buf[--len] = 0;
    if (len > 0 && buf[len-1] == '\r')
        buf[--len] = 0;
    //tc_log_msg(__FILE__, "read from socket: |%s|", buf);

    cmd = buf + strspn(buf, " \t");
    params = cmd + strcspn(cmd, " \t");
    *params++ = 0;
    params += strspn(params, " \t");
    
    if (!*cmd) {  // not strictly necessary, but lines up else if's nicely
        retval = 0;
    } else if (strncasecmp(cmd, "config", 2) == 0) {
        retval = handle_config(params);
    } else if (strncasecmp(cmd, "disable", 2) == 0) {
        retval = handle_disable(params);
    } else if (strncasecmp(cmd, "dump", 2) == 0) {
        dump_vob(client_sock);
        retval = 1;
    } else if (strncasecmp(cmd, "enable", 2) == 0) {
        retval = handle_enable(params);
    } else if (strncasecmp(cmd, "help", 2) == 0) {
        retval = handle_help(params);
    } else if (strncasecmp(cmd, "list", 2) == 0) {
        retval = handle_list(params);
    } else if (strncasecmp(cmd, "load", 2) == 0) {
        retval = handle_load(params);
    } else if (strncasecmp(cmd, "parameters", 3) == 0) {
        retval = handle_parameter(params);
    } else if (strncasecmp(cmd, "pause", 3) == 0) {
        tc_pause_request();
        retval = 1;
    } else if (strncasecmp(cmd, "preview", 3) == 0) {
        retval = handle_preview(params);
    } else if (strncasecmp(cmd, "progress", 3) == 0) {
        tc_progress_meter = !tc_progress_meter;
        retval = 1;
    } else if (strncasecmp(cmd, "quit", 2) == 0
            || strncasecmp(cmd, "exit", 2) == 0) {
        return 0;  // tell caller to close socket
    } else if (strncasecmp(cmd, "unload", 2) == 0) {
	retval = 0;  // FIXME: not implemented
    } else if (strncasecmp(cmd, "version", 2) == 0) {
        sendstr(client_sock, PACKAGE_VERSION "\n");
        retval = 1;
    } else {
	retval = 0;
    }

    sendstr(client_sock, retval ? "OK\n" : "FAILED\n");
    return 1;  // socket remains open
}

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

/* External interfaces. */

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

/**
 * tc_socket_init:  Initialize the socket code, and open a listener socket
 * with the given pathname.
 *
 * Parameters:
 *     socket_path_: Pathname to use for communication socket.
 * Return value:
 *     Nonzero on success, zero on failure.
 * Side effects:
 *     If `socket_path' points to an existing file, the file is removed.
 */

int tc_socket_init(const char *socket_path_)
{
    struct sockaddr_un server_addr;

    client_sock = -1;
    server_sock = -1;

    if (tc_snprintf(socket_path, sizeof(socket_path), "%s", socket_path_) < 0){
        tc_log_error(__FILE__, "Socket pathname too long (1)");
        *socket_path = 0;
        return 0;
    }

    errno = 0;
    if (unlink(socket_path) != 0 && errno != ENOENT) {
        tc_log_error(__FILE__, "Unable to remove \"%s\": %s",
                     socket_path, strerror(errno));
        return 0;
    }

    server_addr.sun_family = AF_UNIX;
    if (tc_snprintf(server_addr.sun_path, sizeof(server_addr.sun_path),
                    "%s", socket_path) < 0
    ) {
        tc_log_error(__FILE__, "Socket pathname too long");
        return 0;
    }
    server_sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server_sock < 0) {
        tc_log_perror(__FILE__, "Unable to create server socket");
        return 0;
    }
    if (bind(server_sock, (struct sockaddr *)&server_addr,
             sizeof(server_addr))
    ) {
        tc_log_perror(__FILE__, "Unable to bind server socket");
        close(server_sock);
        server_sock = -1;
        unlink(socket_path);  // just in case
        return 0;
    }
    if (listen(server_sock, 5) < 0) {
        tc_log_perror(__FILE__, "Unable to activate server socket");
        close(server_sock);
        unlink(socket_path);
        return 0;
    }

    return 1;
}

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

/**
 * tc_socket_fini:  Close the listener and client sockets, if open, and
 * perform any other necessary cleanup.
 *
 * Parameters:
 *     None.
 * Return value:
 *     None.
 */

void tc_socket_fini(void)
{
    if (client_sock >= 0) {
        close(client_sock);
        client_sock = -1;
    }
    if (server_sock >= 0) {
        close(server_sock);
        server_sock = -1;
        unlink(socket_path);
    }
}

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

/**
 * tc_socket_poll_internal:  Check server and (if connected) client sockets for
 * pending events, and process them, with tunable timeout.
 *
 * Parameters:
 *     blocking: if !0, blocks forever caller until next event.
 *               if 0, just check once if there is an event to process
 *               and execute (or just return) immediately.
 * Return value:
 *     None.
 * Preconditions:
 *     valid (!= -1) server socket avalaible.
 */

static void tc_socket_poll_internal(int blocking)
{
    fd_set rfds, wfds;
    char msgbuf[TC_BUF_MAX];
    int maxfd = -1, retval = -1;
    struct timeval tv = {0, 0}, *ptv = (blocking) ?NULL :&tv;

    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    
    FD_SET(server_sock, &rfds);
    maxfd = server_sock;

    if (client_sock >= 0) {
        FD_SET(client_sock, &rfds);
        //FD_SET(client_sock, &wfds);
        if (client_sock > maxfd)
            maxfd = client_sock;
    }
    retval = select(maxfd+1, &rfds, &wfds, NULL, ptv);

    if (retval == 0) {
        /* nothing interesting happened. It happens :) */
        return;
    }
    if (retval < 0 && errno != EINTR) {
        /* EINTR is an expected "exceptional" condition */
        tc_log_warn(__FILE__, "select(): %s", strerror(errno));
        return;
    }

    if (FD_ISSET(server_sock, &rfds)) {
        int newsock = accept(server_sock, NULL, 0);
        if (newsock < 0) {
            tc_log_warn(__FILE__, "Unable to accept new connection: %s",
                        strerror(errno));
            return;
        }
        if (client_sock >= 0) {
            /* We already have a connection, so drop this one */
            close(newsock);
        } else {
            client_sock = newsock;
        }
    }

    if (client_sock >= 0 && FD_ISSET(client_sock, &rfds)) {
        retval = recv(client_sock, msgbuf, sizeof(msgbuf)-1, 0);
        if (retval <= 0) {
            if (retval < 0)
                tc_log_perror(__FILE__, "Unable to read message from socket");
            close(client_sock);
            client_sock = -1;
        } else {
            if (retval > sizeof(msgbuf)-1)  // paranoia
                retval = sizeof(msgbuf)-1;
            msgbuf[retval] = 0;
            if (!handle(msgbuf)) {
                close(client_sock);
                client_sock = -1;
            }
        }
    }
}

/**
 * tc_socket_poll:  Check server and (if connected) client sockets for
 * pending events, and process them.
 * This call should never block caller, returnin immediately if there
 * isn't any event pending.
 *
 * Parameters:
 *     None.
 * Return value:
 *     None.
 */

void tc_socket_poll(void)
{
    if (server_sock < 0 && client_sock < 0) {
        /* Nothing to poll! */
        return;
    }
    tc_socket_poll_internal(0);
}

/**
 * tc_socket_wait:  Wait forever server and (if connected) client sockets
 * for next event, and process the next first one and exit.
 * This call will always block until next event happens.
 *
 * Parameters:
 *     None.
 * Return value:
 *     None.
 */

void tc_socket_wait(void)
{
    if (server_sock < 0 && client_sock < 0) {
        pause();
        /* if no server socket is avalaible (= no tc_socket_init
         * called before), nothing will ever happen, so
         * we must block forever to satisfy function requirements.
         */
        return;
    }
    tc_socket_poll_internal(1);
}


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

/**
 * tc_socket_submit:  Send a string to the socket.  Does nothing if no
 * socket is open.
 *
 * Parameters:
 *     str: String to send.
 * Return value:
 *     None.
 */

void tc_socket_submit(const char *str)
{
    if (socket >= 0)
        sendstr(client_sock, str);
}

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

/*
 * Local variables:
 *   c-file-style: "stroustrup"
 *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
 *   indent-tabs-mode: nil
 * End:
 *
 * vim: expandtab shiftwidth=4:
 */
