/* $Id: rptp.c,v 1.6 1999/03/21 00:45:08 boyns Exp $ */

/*
 * Copyright (C) 1993-99 Mark R. Boyns <boyns@doit.org>
 *
 * This file is part of rplay.
 *
 * rplay 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * rplay 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 rplay; see the file COPYING.  If not, write to the
 * Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "version.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_LIBREADLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif /* HAVE_LIBREADLINE */
#include "rplay.h"
#include "getopt.h"

typedef struct
{
    char *name;
    int min_args;
    int max_args;
    char *usage;
#ifdef __STDC__
    void (*func) (int argc, char **argv);
#else
    void (*func) ();
#endif
}
COMMAND;

#ifdef __STDC__
void command_open(int argc, char **argv);
void command_close(int argc, char **argv);
void command_play(int argc, char **argv);
void command_help(int argc, char **argv);
void command_list(int argc, char **argv);
void command_quit(int argc, char **argv);
void command_put(int argc, char **argv);
void command_get(int argc, char **argv);
void command_unknown(int argc, char **argv);
void command_set(int argc, char **argv);
void command_status(int argc, char **argv);
void command_generic(int argc, char **argv);
void command_volume(int argc, char **argv);
void command_skip(int argc, char **argv);
void command_set(int argc, char **argv);
void command_monitor(int argc, char **argv);
void argv_to_command(char **argv);
int connected();
void done(int exit_value);
void usage();
void do_application();
void do_error(char *repsonse);
#else
void command_open( /* int argc, char **argv */ );
void command_close( /* int argc, char **argv */ );
void command_play( /* int argc, char **argv */ );
void command_help( /* int argc, char **argv */ );
void command_list( /* int argc, char **argv */ );
void command_quit( /* int argc, char **argv */ );
void command_put( /* int argc, char **argv */ );
void command_get( /* int argc, char **argv */ );
void command_unknown( /* int argc, char **argv */ );
void command_status( /* int argc, char **argv */ );
void command_generic( /* int argc, char **argv */ );
void command_volume( /* int argc, char **argv */ );
void command_skip( /* int argc, char **argv */ );
void command_set( /* int argc, char **argv */ );
void command_monitor( /* int argc, char **argv */ );
void argv_to_command( /* char **argv */ );
int connected();
void done( /* int exit_value */ );
void usage();
void do_application();
void do_error( /* char *repsonse */ );
#endif

COMMAND commands[] =
{
    "access", 0, 0, "", command_generic,
    "close", 0, 0, "", command_close,
    "continue", 1, -1, "#id|sound ...", command_play,
    "find", 1, 1, "sound", command_generic,
    "get", 1, 2, "sound [filename]", command_get,
    "help", 0, 1, "[command]", command_help,
    "info", 1, 1, "sound", command_generic,
    "list", 0, 1, "[connections|hosts|servers|spool|sounds]", command_list,
    "monitor", 0, 0, "", command_monitor,
    "open", 1, 2, "hostname [port]", command_open,
    "pause", 1, -1, "#id|sound ...", command_play,
    "play", 1, -1, "[options] sound ...", command_play,
    "put", 1, 1, "sound", command_put,
    "quit", 0, 0, "", command_quit,
    "reset", 0, 0, "", command_generic,
    "set", 1, -1, "name=value", command_set,
    "skip", 0, 2, "[#id] [[+|-]count]", command_skip,
    "status", 0, 0, "", command_status,
    "stop", 1, -1, "#id|sound ...", command_play,
    "version", 0, 0, "", command_generic,
    "volume", 0, 1, "[[+|-]volume]", command_volume,
    "wait", 1, -1, "#id|command|event-list", command_generic,
};

#define NCOMMANDS	(sizeof(commands)/sizeof(COMMAND))

static struct option longopts[] =
{
    {"help", no_argument, NULL, 1},
    {"host", required_argument, NULL, 'h'},
    {"port", required_argument, NULL, 'p'},
    {"prompt", required_argument, NULL, 2},
    {"raw", no_argument, NULL, 'r'},
    {"version", no_argument, NULL, 'v'},
    {NULL, 0, NULL, 0}
};

int rptp_fd = -1;
char rptp_buf[4096];
char command[RPTP_MAX_LINE];
char response[RPTP_MAX_LINE];
char *prompt = "rptp> ";
int interactive = 1;
int raw = 0;

extern int optind;
extern char *optarg;

#ifdef __STDC__
main(int argc, char **argv)
#else
main(argc, argv)
    int argc;
    char **argv;
#endif
{
    char buf[256];
    int i;
    int port = RPTP_PORT;
    int c;
    char *av[RPTP_MAX_ARGS], *p, *host = NULL;
    int ac, first;
    char numeric_string[128];

    while ((c = getopt_long(argc, argv, "+h:p:rv", longopts, 0)) != -1)
    {
	switch (c)
	{
	case 0:
	    /* getopt has processed a long-named option -- do nothing */
	    break;

	case 1:		/* --help */
	    usage();
	    done(0);

	case 2:		/* --prompt */
	    prompt = optarg;
	    break;

	case 'h':
	    host = optarg;
	    break;

	case 'p':
	    port = atoi(optarg);
	    break;

	case 'r':
	    raw++;
	    break;

	case 'v':
	    printf("rplay %s\n", RPLAY_VERSION);
	    done(0);

	default:
	    fprintf(stderr, "Try `rptp --help' for more information.\n");
	    exit(1);
	}
    }

    if (optind != argc)
    {
	interactive = 0;
    }

    if (host == NULL)
    {
	host = rplay_default_host();
    }

    rptp_fd = rptp_open(host, port, response, sizeof(response));
    if (rptp_fd < 0)
    {
	rptp_perror("open");
    }
    else if (interactive)
    {
	if (raw)
	{
	    printf("%s\n", response);
	}
	else
	{
	    if (strchr(response, '='))
	    {
		rptp_parse(response, 0);
		printf("%s rplayd %s connected\n",
		       rptp_parse(0, "host"),
		       rptp_parse(0, "version"));
	    }
	    else
	    {
		printf("Connected to %s port %d.\n", host, port);
		printf("%s\n", response + 1);
	    }
	}
    }

    do_application();

    do
    {
	if (interactive)
	{
	    if (!raw)
	    {
#ifdef HAVE_LIBREADLINE
		p = readline(prompt);
		if (!p)
		{
		    done(0);
		}
		add_history(p);
		strcpy(buf, p);
#else
		printf(prompt);
		fflush(stdout);
#endif
	    }

#ifndef HAVE_LIBREADLINE
	    if (fgets(buf, sizeof(buf), stdin) == NULL)
	    {
		done(0);
	    }
#endif
	    first = 1;
	    ac = 0;
	    while ((p = strtok(first ? buf : NULL, " \t\r\n")))
	    {
		av[ac++] = p;
		first = 0;
	    }
	    av[ac] = NULL;
	}
	else
	{
	    int i;

	    ac = 0;
	    for (i = optind; i < argc; i++)
	    {
		av[ac++] = argv[i];
	    }
	    av[ac] = NULL;
	}

	if (av[0] == NULL)
	{
	    continue;
	}

	for (i = 0; i < NCOMMANDS; i++)
	{
	    if (strcasecmp(commands[i].name, av[0]) == 0)
	    {
		if ((commands[i].min_args >= 0 && ac - 1 < commands[i].min_args)
		    || (commands[i].max_args >= 0 && ac - 1 > commands[i].max_args))
		{
		    printf("Usage: %s %s\n", commands[i].name, commands[i].usage);
		}
		else
		{
		    (*commands[i].func) (ac, av);
		}
		break;
	    }
	}
	if (i == NCOMMANDS)
	{
	    command_unknown(ac, av);
	}
    }
    while (interactive);

    done(0);
}

#ifdef __STDC__
void
argv_to_command(char **argv)
#else
void
argv_to_command(argv)
    char **argv;
#endif
{
    command[0] = '\0';
    for (; *argv; argv++)
    {
	strcat(command, *argv);
	if (*(argv + 1))
	{
	    strcat(command, " ");
	}
    }
}

int
connected()
{
    if (rptp_fd != -1)
    {
	return 1;
    }
    else
    {
	printf("You're not connected, use `open' first.\n");
	return 0;
    }
}

#ifdef __STDC__
void
command_open(int argc, char **argv)
#else
void
command_open(argc, argv)
    int argc;
    char **argv;
#endif
{
    int port;

    if (rptp_fd != -1)
    {
	printf("You're already connected, use `close' first.\n");
    }
    else
    {
	port = argc == 3 ? atoi(argv[2]) : RPTP_PORT;
	rptp_fd = rptp_open(argv[1], port, response, sizeof(response));
	if (rptp_fd < 0)
	{
	    rptp_perror("open");
	}
	else
	{
	    if (raw)
	    {
		printf("%s\n", response);
	    }
	    else
	    {
		if (strchr(response, '='))
		{
		    printf("%s rplayd %s connected\n",
			   rptp_parse(response, "host"),
			   rptp_parse(0, "version"));
		}
		else
		{
		    printf("Connected to %s port %d.\n", argv[1], port);
		    printf("%s\n", response + 1);
		}
	    }
	    do_application();
	}
    }
}

#ifdef __STDC__
void
command_close(int argc, char **argv)
#else
void
command_close(argc, argv)
    int argc;
    char **argv;
#endif
{
    if (connected())
    {
	rptp_close(rptp_fd);
	rptp_fd = -1;
	if (interactive)
	{
	    printf("Connection closed.\n");
	}
    }
}

#ifdef __STDC__
void
command_play(int argc, char **argv)
#else
void
command_play(argc, argv)
    int argc;
    char **argv;
#endif
{
    argv_to_command(argv);

    if (!connected())
    {
	return;
    }

    switch (rptp_command(rptp_fd, command, response, sizeof(response)))
    {
    case -1:
	rptp_perror(argv[0]);
	command_close(argc, argv);
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	break;
    }

    if (raw)
    {
	printf("%s\n", response);
    }
}

#ifdef __STDC__
void
command_generic(int argc, char **argv)
#else
void
command_generic(argc, argv)
    int argc;
    char **argv;
#endif
{
    argv_to_command(argv);

    if (!connected())
    {
	return;
    }

    switch (rptp_command(rptp_fd, command, response, sizeof(response)))
    {
    case -1:
	rptp_perror(argv[0]);
	command_close(argc, argv);
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	break;
    }

    printf("%s\n", raw ? response : response + 1);
}

#ifdef __STDC__
void
command_help(int argc, char **argv)
#else
void
command_help(argc, argv)
    int argc;
    char **argv;
#endif
{
    int i;

    if (argc == 2)
    {
	for (i = 0; i < NCOMMANDS; i++)
	{
	    if (strcasecmp(commands[i].name, argv[1]) == 0)
	    {
		printf("Usage: %s %s\n", commands[i].name, commands[i].usage);
		return;
	    }
	}
	command_unknown(argc, argv);
    }
    else
    {
	printf("access   Display remote access permissions.\n");
	printf("close    Close the current server connection.\n");
	printf("continue Continue paused sounds.\n");
	printf("find     Search for a sound.\n");
	printf("get      Retrieve a sound.\n");
	printf("help     Display help information.\n");
	printf("info     Display sound information.\n");
	printf("list     Display various server information.\n");
	printf("open     Connect to a server.\n");
	printf("pause    Pause sounds that are playing.\n");
	printf("play     Play sounds\n");
	printf("put      Send a sound.\n");
	printf("quit     Terminate the rptp session.\n");
	printf("reset    Tell the server to reset itself.\n");
	printf("set      Change server settings.\n");
	printf("skip     Skip sounds in a sound list.\n");
	printf("status   Display server statistics.\n");
	printf("stop     Stop sounds that are playing.\n");
	printf("version  Display the version of the server.\n");
	printf("volume   Get and set the volume of the audio device.\n");
	printf("wait     Wait for a spool id, volume change, or command execution.\n");
    }
}

#ifdef __STDC__
void
command_list(int argc, char **argv)
#else
void
command_list(argc, argv)
    int argc;
    char **argv;
#endif
{
    int n;

    if (!connected())
    {
	return;
    }

    argv_to_command(argv);

    switch (rptp_command(rptp_fd, command, response, sizeof(response)))
    {
    case -1:
	rptp_perror(argv[0]);
	command_close(argc, argv);
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	if (raw)
	{
	    printf("%s\n", response);
	}
	break;
    }

    for (;;)
    {
	n = rptp_getline(rptp_fd, rptp_buf, sizeof(rptp_buf));
	if (n < 0)
	{
	    rptp_perror("list");
	    command_close(argc, argv);
	    break;
	}
	if (strcmp(rptp_buf, ".") == 0)
	{
	    if (raw)
	    {
		printf("%s\n", rptp_buf);
	    }
	    break;
	}
	printf("%s\n", rptp_buf);
    }
}

#ifdef __STDC__
void
command_quit(int argc, char **argv)
#else
void
command_quit(argc, argv)
    int argc;
    char **argv;
#endif
{
    if (rptp_fd != -1)
    {
	rptp_close(rptp_fd);
	printf("Connection closed.\n");
    }
    done(0);
}

#ifdef __STDC__
void
command_put(int argc, char **argv)
#else
void
command_put(argc, argv)
    int argc;
    char **argv;
#endif
{
    FILE *fp;
    int size, n, nwritten;
    struct stat st;
    char line[RPTP_MAX_LINE];
    int total_size;

    if (!connected())
    {
	return;
    }

    if (stat(argv[1], &st) < 0)
    {
	perror(argv[1]);
	return;
    }
    fp = fopen(argv[1], "r");
    if (fp == NULL)
    {
	perror(argv[1]);
	return;
    }
    size = st.st_size;

    sprintf(line, "put sound=%s size=%d", argv[1], size);

    switch (rptp_command(rptp_fd, line, response, sizeof(response)))
    {
    case -1:
	rptp_perror(argv[0]);
	command_close(argc, argv);
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	break;
    }

    if (raw)
    {
	printf("%s\n", response);
    }

    total_size = size;
    while (size > 0)
    {
	n = fread(rptp_buf, 1, sizeof(rptp_buf), fp);
	nwritten = rptp_write(rptp_fd, rptp_buf, n);
	if (nwritten != n)
	{
	    rptp_perror("put");
	    command_close(argc, argv);
	    break;
	}
	size -= nwritten;

	sprintf(line, "\r%s %d/%d %d%%", argv[1],
		total_size - size, total_size,
		(int)(((float)(total_size-size)/total_size)*100));
	write(2, line, strlen(line));
    }
    write(2, "\n", 1);
    fclose(fp);
}

#ifdef __STDC__
void
command_get(int argc, char **argv)
#else
void
command_get(argc, argv)
    int argc;
    char **argv;
#endif
{
    FILE *fp;
    char *filename;
    char *p;
    int size, n, nread, total_size;
    char line[RPTP_MAX_LINE];

    if (!connected())
    {
	return;
    }

    if (argc == 3)
    {
	filename = argv[2];
	argv[2] = NULL;
    }
    else
    {
	filename = argv[1];
    }

    sprintf(line, "get sound=%s", argv[1]);

    switch (rptp_command(rptp_fd, line, response, sizeof(response)))
    {
    case -1:
	rptp_perror(argv[0]);
	command_close(argc, argv);
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	break;
    }

    if (raw)
	printf("%s\n", response);

    fp = fopen(filename, "w");
    if (fp == NULL)
    {
	perror(filename);
	return;
    }

    if (strchr(response, '='))
    {
	size = atoi(rptp_parse(response, "size"));
    }
    else
    {
	p = strtok(response + 1, " ");
	size = atoi(strtok(NULL, "\r\n"));
    }

    total_size = size;
    while (size > 0)
    {
	n = MIN(sizeof(rptp_buf), size);
	nread = rptp_read(rptp_fd, rptp_buf, n);
	if (nread != n)
	{
	    rptp_perror("get");
	    break;
	}
	fwrite(rptp_buf, 1, n, fp);
	size -= n;
	
	sprintf(line, "\r%s %d/%d %d%%", filename,
		total_size - size, total_size,
		(int)(((float)(total_size-size)/total_size)*100));
	write(2, line, strlen(line));
    }
    write(2, "\n", 1);
    fclose(fp);
}

#ifdef __STDC__
void
command_unknown(int argc, char **argv)
#else
void
command_unknown(argc, argv)
    int argc;
    char **argv;
#endif
{
    printf("unknown command `%s'.\n", argv[0]);
}

#ifdef __STDC__
void
command_status(int argc, char **argv)
#else
void
command_status(argc, argv)
    int argc;
    char **argv;
#endif
{
    argv_to_command(argv);

    if (!connected())
    {
	return;
    }

    switch (rptp_command(rptp_fd, command, response, sizeof(response)))
    {
    case -1:
	rptp_perror(argv[0]);
	command_close(argc, argv);
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	break;
    }

    if (raw)
    {
	printf("%s\n", response);
    }
    else
    {
	int first = 1;
	char *name, *value;

	while (name = rptp_parse(first ? response : 0, 0))
	{
	    first = 0;
	    value = rptp_parse(0, name);
	    printf("%s=%s\n", name, value);
	}
    }
}

#ifdef __STDC__
void
command_volume(int argc, char **argv)
#else
void
command_volume(argc, argv)
    int argc;
    char **argv;
#endif
{
    char *volume;

    if (!connected())
    {
	return;
    }

    if (argc == 2)
    {
	sprintf(command, "set volume=%s", argv[1]);
    }
    else
    {
	sprintf(command, "set volume");
    }

    switch (rptp_command(rptp_fd, command, response, sizeof(response)))
    {
    case -1:
	rptp_perror(argv[0]);
	command_close(argc, argv);
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	break;
    }

    volume = rptp_parse(response, "volume");
    if (volume && *volume)
    {
	printf("volume=%s\n", volume);
    }
    else
    {
	printf("unknown response `%s'\n", response);
    }
}

#ifdef __STDC__
void
command_skip(int argc, char **argv)
#else
void
command_skip(argc, argv)
    int argc;
    char **argv;
#endif
{
    char *value;

    if (!connected())
    {
	return;
    }

    if (strchr(command, '='))
    {
	/* Leave command alone. */
    }
    else if (argc == 3)
    {
	sprintf(command, "skip id=%s count=%s", argv[1], argv[2]);
    }
    else if (argc == 2)
    {
	sprintf(command, "skip id=%s count=1", argv[1]);
    }
    else
    {
	sprintf(command, "skip id=#0 count=1");
    }

    switch (rptp_command(rptp_fd, command, response, sizeof(response)))
    {
    case -1:
	rptp_perror(argv[0]);
	command_close(argc, argv);
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	break;
    }

    value = rptp_parse(response, "message");
    if (value && *value)
    {
	printf("%s\n", value);
    }
    else
    {
	printf("unknown response `%s'\n", response);
    }
}

#ifdef __STDC__
void
command_set(int argc, char **argv)
#else
void
command_set(argc, argv)
    int argc;
    char **argv;
#endif
{
    char *value;

    if (!connected())
    {
	return;
    }

    argv_to_command(argv);

    switch (rptp_command(rptp_fd, command, response, sizeof(response)))
    {
    case -1:
	rptp_perror(argv[0]);
	command_close(argc, argv);
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	break;
    }
}

#ifdef __STDC__
void
command_monitor(int argc, char **argv)
#else
void
command_monitor(argc, argv)
    int argc;
    char **argv;
#endif
{
    char *value, *p;
    char buf[8192];
    char line[80];
    int n, size;
    int sample_rate, precision, channels, sample_size;
    
    if (!connected())
    {
	return;
    }

    sprintf(command, "monitor");

    switch (rptp_command(rptp_fd, command, response, sizeof(response)))
    {
    case -1:
	rptp_perror(argv[0]);
	command_close(argc, argv);
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	break;
    }

    p = strtok(rptp_parse(response, "audio-info"), ",");
    // input_format

    p = strtok(NULL, ",");
    if (p) sample_rate = atoi(p);

    p = strtok(NULL, ",");
    if (p) precision = atoi(p);

    p = strtok(NULL, ",");
    if (p) channels = atoi(p);

    sample_size = precision/8 * channels;

    fprintf(stderr, "%dhz %dbit %s\n", sample_rate, precision,
	    channels == 2 ? "stereo" : "mono");

    while ((n = read(rptp_fd, buf, sizeof(buf))) > 0)
    {
	write(1, buf, n);
	size += n;
    }
    close(rptp_fd);
}

void
do_application()
{
    sprintf(command, "set application=\"rptp %s\"", RPLAY_VERSION);

    switch (rptp_command(rptp_fd, command, response, sizeof(response)))
    {
    case -1:
	rptp_perror("application");
	rptp_close(rptp_fd);
	rptp_fd = -1;
	return;

    case 1:
	do_error(response);
	return;

    case 0:
	break;
    }
}

#ifdef __STDC__
void
do_error(char *response)
#else
void
do_error(response)
    char *response;
#endif
{
    if (raw)
    {
	printf("%s\n", response);
    }
    else
    {
	char *error;

	error = rptp_parse(response, "error");
	if (!error || !*error)
	{
	    error = response;
	}
	printf("%s\n", error);
    }
}

void
usage()
{
    printf("\nrplay %s\n\n", RPLAY_VERSION);
    printf("usage: rptp [options] [command]\n");
    printf("--help\n");
    printf("\tDisplay helpful information.\n");
    printf("\n");

    printf("-h HOST, --host=HOST\n");
    printf("\tSpecify the RPTP host, default = %s.\n",
	   rplay_default_host());
    printf("\n");

    printf("-p PORT, --port=PORT\n");
    printf("\tUse PORT instead of the default RPTP port, default = %d.\n", RPTP_PORT);
    printf("\n");

    printf("-r, --raw\n");
    printf("\tEnable raw RPTP mode.\n");
    printf("\n");

    printf("-v, --version\n");
    printf("\tDisplay rplay version information.\n");
}

#ifdef __STDC__
void
done(int exit_value)
#else
void
done(exit_value)
    int exit_value;
#endif
{
    exit(exit_value);
}
