/*
 * oscsend - Send OpenSound Control message.
 *
 * Copyright (C) 2008 Kentaro Fukuchi <kentaro@fukuchi.org>
 *
 * 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 <ctype.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"
           "or     oscsend url 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"
           "url     : specifies the destination parameters using a liblo URL.\n"
           "          e.g. UDP        \"osc.udp://localhost:9000\"\n"
           "               Multicast  \"osc.udp://224.0.1.9:9000\"\n"
           "               TCP        \"osc.tcp://localhost:9000\"\n"
           "               File       \"file:///tmp/dump.osc\"\n"
           "               stdout     -\n\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[0] <- types
     * argv[1..] <- values
     */
    int i, argi;
    lo_message message;
    const char *types, *arg;
    int values;

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

    argi = 1;
    arg = NULL;
    for (i = 0; i < values; i++) {
		switch(types[i]) {
		case LO_INT32:
		case LO_FLOAT:
		case LO_STRING:
		case LO_BLOB:
		case LO_INT64:
		case LO_TIMETAG:
		case LO_DOUBLE:
		case LO_SYMBOL:
		case LO_CHAR:
		case LO_MIDI:
			arg = argv[argi];
			if (arg == NULL) {
				fprintf(stderr, "Value #%d is not given.\n", i + 1);
				goto EXIT;
			}
			break;
		default:
			break;
		}
        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 = NULL;
    lo_message message;
    int ret, i=1, dump_to_file=0, dump_to_stdout=0;
    char file_uri[256];

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

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

    if (strstr(argv[i], "://")!=0) {
        if(strncmp(argv[i], "file://", 7)==0 && strlen(argv[i])>7) {
            dump_to_file=1;
            memset(file_uri, 0, sizeof(file_uri));
            strncpy(file_uri, argv[i]+7, sizeof(file_uri)-1);
        }
        else {
            target = lo_address_new_from_url(argv[i]);
            if (target == NULL) {
                fprintf(stderr, "Failed to open %s\n", argv[i]);
                exit(1);
            }
        }
        i++;
    }
    else if(strncmp(argv[i], "-", 1)==0 && strlen(argv[i])==1) {
        dump_to_stdout=1;
        i++;
    }
    else if (argv[i+1] == NULL) {
        fprintf(stderr, "No port number is given.\n");
        exit(1);
    }
    else {
        target = lo_address_new(argv[i], argv[i+1]);
        if (target == NULL) {
            fprintf(stderr, "Failed to open %s:%s\n", argv[i], argv[i+1]);
            exit(1);
        }
        i += 2;
    }

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

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

    if(!dump_to_file && !dump_to_stdout) {
        lo_address_set_ttl(target, 1);

        ret = lo_send_message(target, argv[i], message);
        if (ret == -1) {
            fprintf(stderr, "An error occurred: %s\n",
                    lo_address_errstr(target));
            lo_message_free(message);
            exit(1);
        }
    }
    else {
	FILE *fout = NULL;

        if(dump_to_file) {
            FILE *f=fopen(file_uri, "w+");
            if(f == NULL) {
                fprintf(stderr, "An error occurred: Could not open file for writing OSC message: '%s'\n",
                    file_uri);
                lo_message_free(message);
                exit(1);
            }
            fout=f;
        }
        else if(dump_to_stdout) {
            fout=stdout;
        }

        size_t size;
        void *msg_ptr=lo_message_serialise(message, argv[i], NULL, &size);
        fwrite(msg_ptr, 1, size, fout);
        fflush(fout);
        fclose(fout);
        free(msg_ptr);
    }

    lo_message_free(message);

    return 0;
}
