/*
 * oscsend - Send OpenSound Control message.
 *
 * Copyright (C) 2008 Kentaro Fukuchi <fukuchi@megaui.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * TODO:
 * - support binary blob.
 * - support TimeTag.
 * - receive replies.
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <errno.h>
#include <config.h>
#include <limits.h>
#include <lo/lo.h>

void usage(void)
{
	printf("oscsend version %s\n"
		   "Copyright (C) 2008 Kentaro Fukuchi\n\n"
		   "Usage: oscsend hostname port address types values...\n"
		   "Send OpenSound Control message via UDP.\n\n"
		   "Description\n"
		   "hostname: specifies the remote host's name.\n"
		   "port    : specifies the port number to connect to the remote host.\n"
		   "address : the OSC address where the message to be sent.\n"
		   "types   : specifies the types of the following values.\n",
		   VERSION);
	printf("          %c - 32bit integer\n", LO_INT32);
	printf("          %c - 64bit integer\n", LO_INT64);
	printf("          %c - 32bit floating point number\n", LO_FLOAT);
	printf("          %c - 64bit (double) floating point number\n", LO_DOUBLE);
	printf("          %c - string\n", LO_STRING);
	printf("          %c - symbol\n", LO_SYMBOL);
	printf("          %c - char\n", LO_CHAR);
	printf("          %c - 4 byte midi packet (8 digits hexadecimal)\n", LO_MIDI);
	printf("          %c - TRUE (no value required)\n", LO_TRUE);
	printf("          %c - FALSE (no value required)\n", LO_FALSE);
	printf("          %c - NIL (no value required)\n", LO_NIL);
	printf("          %c - INFINITUM (no value required)\n", LO_INFINITUM);
	printf("values  : space separated values.\n\n"
		   "Example\n"
		   "$ oscsend localhost 7777 /sample/address %c%c%c%c 1 3.14 hello\n",
		   LO_INT32, LO_TRUE, LO_FLOAT, LO_STRING);
}

lo_message create_message(char **argv)
{
	/* Note:
	 * argv[1] <- hostname
	 * argv[2] <- port
	 * argv[3] <- OSC address
	 * argv[4] <- types
	 * argv[5..] <- values
	 */
	int i, argi;
	lo_message message;
	const char *types, *arg;
	int values;

	message = lo_message_new();
	if(argv[4] == NULL) {
		/* empty message is allowed. */
		values = 0;
	} else {
		types = argv[4];
		values = strlen(types);
	}

	argi = 5;
	for(i=0; i<values; i++) {
		arg = argv[argi];
		if(arg == NULL) {
			fprintf(stderr, "Value #%d is not given.\n", i + 1);
			goto EXIT;
		}
		switch(types[i]) {
			case LO_INT32:
			{
				char *endp;
				int64_t v;

				v = strtol(arg, &endp, 10);
				if(*endp != '\0') {
					fprintf(stderr, "An invalid value was given: '%s'\n", arg);
					goto EXIT;
				}
				if((v == LONG_MAX || v == LONG_MIN) && errno == ERANGE) {
					fprintf(stderr, "Value out of range: '%s'\n", arg);
					goto EXIT;
				}
				if(v > INT_MAX || v < INT_MIN) {
					fprintf(stderr, "Value out of range: '%s'\n", arg);
					goto EXIT;
				}
				lo_message_add_int32(message, (int32_t)v);
				argi++;
				break;
			}
			case LO_INT64:
			{
				char *endp;
				int64_t v;

				v = strtoll(arg, &endp, 10);
				if(*endp != '\0') {
					fprintf(stderr, "An invalid value was given: '%s'\n", arg);
					goto EXIT;
				}
				if((v == LONG_MAX || v == LONG_MIN) && errno == ERANGE) {
					fprintf(stderr, "Value out of range: '%s'\n", arg);
					goto EXIT;
				}
				lo_message_add_int64(message, v);
				argi++;
				break;
			}
			case LO_FLOAT:
			{
				char *endp;
				float v;

#ifdef __USE_ISOC99
				v = strtof(arg, &endp);
#else
				v = (float)strtod(arg, &endp);
#endif /* __USE_ISOC99 */
				if(*endp != '\0') {
					fprintf(stderr, "An invalid value was given: '%s'\n", arg);
					goto EXIT;
				}
				lo_message_add_float(message, v);
				argi++;
				break;
			}
			case LO_DOUBLE:
			{
				char *endp;
				double v;

				v = strtod(arg, &endp);
				if(*endp != '\0') {
					perror(NULL);
					fprintf(stderr, "An invalid value was given: '%s'\n", arg);
					goto EXIT;
				}
				lo_message_add_double(message, v);
				argi++;
				break;
			}
			case LO_STRING:
				lo_message_add_string(message, arg);
				argi++;
				break;
			case LO_SYMBOL:
				lo_message_add_symbol(message, arg);
				argi++;
				break;
			case LO_CHAR:
				lo_message_add_char(message, arg[0]);
				argi++;
				break;
			case LO_MIDI:
			{
				unsigned int midi;
				uint8_t packet[4];
				int ret;

				ret = sscanf(arg, "%08x", &midi);
				if(ret != 1) {
					fprintf(stderr, "An invalid hexadecimal value was given: '%s'\n", arg);
					goto EXIT;
				}
				packet[0] = (midi>>24) & 0xff;
				packet[1] = (midi>>16) & 0xff;
				packet[2] = (midi>> 8) & 0xff;
				packet[3] = midi & 0xff;
				lo_message_add_midi(message, packet);
				argi++;
				break;
			}
			case LO_TRUE:
				lo_message_add_true(message);
				break;
			case LO_FALSE:
				lo_message_add_false(message);
				break;
			case LO_NIL:
				lo_message_add_nil(message);
				break;
			case LO_INFINITUM:
				lo_message_add_infinitum(message);
				break;
			default:
				fprintf(stderr, "Type '%c' is not supported or invalid.\n", types[i]);
				goto EXIT;
				break;
		}
	}

	return message;
EXIT:
	lo_message_free(message);
	return NULL;
}

int main(int argc, char **argv)
{
	lo_address target;
	lo_message message;
	int ret;

	if(argc < 4) {
		usage();
		exit(1);
	}

	if(argv[1] == NULL) {
		fprintf(stderr, "No hostname is given.\n");
		exit(1);
	}
	if(argv[2] == NULL) {
		fprintf(stderr, "No port number is given.\n");
		exit(1);
	}

	target = lo_address_new(argv[1], argv[2]);
	if(target == NULL) {
		fprintf(stderr, "Failed to open %s:%s\n", argv[1], argv[2]);
		exit(1);
	}

	if(argv[3] == NULL) {
		fprintf(stderr, "No path is given.\n");
		exit(1);
	}

	message = create_message(argv);
	if(message == NULL) {
		fprintf(stderr, "Failed to create OSC message.\n");
		exit(1);
	}

	ret = lo_send_message(target, argv[3], message);
	if(ret == -1) {
		fprintf(stderr, "An error occured: %s\n", lo_address_errstr(target));
		exit(1);
	}

	return 0;
}
