#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>

#include <jack/jack.h>
#include <jack/metadata.h>
#include <jack/uuid.h>
#include <jack/session.h>

static int subject_is_client = 0;
static int subject_is_port = 0;
static jack_uuid_t uuid = JACK_UUID_EMPTY_INITIALIZER;
static char* subject = NULL;

static void
show_usage (void)
{
        fprintf (stderr, "\nUsage: jack_property [options] UUID [ key [ value [ type ] ] ]\n");
        fprintf (stderr, "Set/Display JACK properties (metadata).\n\n");
        fprintf (stderr, "Set options:\n");
        fprintf (stderr, "        -s, --set		Set property \"key\" to \"value\" for \"UUID\" with optional MIME type \"type\"\n");
        fprintf (stderr, "        -d, --delete		Remove/delete property \"key\" for \"UUID\"\n");
        fprintf (stderr, "        -d, --delete UUID	Remove/delete all properties for \"UUID\"\n");
        fprintf (stderr, "        -D, --delete-all	Remove/delete all properties\n");
        fprintf (stderr, "        --client		Interpret UUID as a client name, not a UUID\n");
        fprintf (stderr, "        --port		\tInterpret UUID as a port name, not a UUID\n");
        fprintf (stderr, "\nDisplay options:\n");
        fprintf (stderr, "        -l			Show all properties\n");
        fprintf (stderr, "        -l, --list UUID	\tShow value for all properties of UUID\n");
        fprintf (stderr, "        -l, --list UUID key	Show value for key of UUID\n");
        fprintf (stderr, "\nFor more information see https://jackaudio.org/\n");
}

static int
get_subject (jack_client_t* client, char* argv[], int* optind)
{
        if (subject_is_client) {
                char* cstr = argv[(*optind)++];
                char* ustr;

                if ((ustr = jack_get_uuid_for_client_name (client, cstr)) == NULL) {
                        fprintf (stderr, "cannot get UUID for client named %s\n", cstr);
                        return -1;
                }

                if (jack_uuid_parse (ustr, &uuid)) {
                        fprintf (stderr, "cannot parse client UUID as UUID '%s' '%s'\n", cstr, ustr);
                        return -1;
                }

                subject = cstr;

        } else if (subject_is_port) {

                char* pstr = argv[(*optind)++];
                jack_port_t* port;

                if ((port = jack_port_by_name (client, pstr)) == NULL) {
                        fprintf (stderr, "cannot find port name %s\n", pstr);
                        return -1;
                }

                uuid = jack_port_uuid (port);
                subject = pstr;

        } else {
                char* str = argv[(*optind)++];

                if (jack_uuid_parse (str, &uuid)) {
                        fprintf (stderr, "cannot parse subject as UUID\n");
                        return -1;
                }

                subject = str;
        }

        return 0;
}

int main (int argc, char* argv[])
{
        jack_client_t* client = NULL;
        jack_options_t options = JackNoStartServer;
        char* key = NULL;
        char* value = NULL;
        char* type = NULL;
        int set = 1;
        int delete = 0;
        int delete_all = 0;
        int c;
        int option_index;
        extern int optind;
        struct option long_options[] = {
            { "set", 0, 0, 's' },
            { "delete", 0, 0, 'd' },
            { "delete-all", 0, 0, 'D' },
            { "list", 0, 0, 'l' },
            { "client", 0, 0, 'c' },
            { "port", 0, 0, 'p' },
            { 0, 0, 0, 0 }
        };

        if (argc < 2) {
                show_usage ();
                exit (1);
        }

        while ((c = getopt_long (argc, argv, "sdDlaApc", long_options, &option_index)) >= 0) {
            switch (c) {
            case 's':
                        if (argc < 5) {
                                show_usage ();
                                exit (1);
                        }
                        set = 1;
                        break;
                case 'd':
                        if (argc < 3) {
                                show_usage ();
                                return 1;
                        }
                        set = 0;
                        delete = 1;
                        break;

                case 'D':
                        delete = 0;
                        set = 0;
                        delete_all = 1;
                        break;

                case 'l':
                        set = 0;
                        delete = 0;
                        delete_all = 0;
                        break;

                case 'p':
                        subject_is_port = 1;
                        break;

                case 'c':
                        subject_is_client = 1;
                        break;

                case '?':
                default:
                        show_usage ();
                        exit (1);
                }
        }

        if ((client = jack_client_open ("jack-property", options, NULL)) == 0) {
                fprintf (stderr, "Cannot connect to JACK server\n");
                exit (1);
        }

        if (delete_all) {

                if (jack_remove_all_properties (client) == 0) {
                        printf ("JACK metadata successfully delete\n");
                        exit (0);
                }
                exit (1);
        }

        if (delete) {

                int args_left = argc - optind;

                if (args_left < 1) {
                        show_usage ();
                        exit (1);
                }

                /* argc == 3: delete all properties for a subject
                   argc == 4: delete value of key for subject
                */

                if (args_left >= 2) {

                        if (get_subject (client, argv, &optind)) {
                                return 1;
                        }

                        key = argv[optind++];

                        if (jack_remove_property (client, uuid, key)) {
                                fprintf (stderr, "\"%s\" property not removed for %s\n", key, subject);
                                exit (1);
                        }

                } else {

                        if (get_subject (client, argv, &optind)) {
                                return 1;
                        }

                        if (jack_remove_properties (client, uuid) < 0) {
                                fprintf (stderr, "cannot remove properties for UUID %s\n", subject);
                                exit (1);
                        }
                }

        }  else if (set) {

                int args_left = argc - optind;

                if (get_subject (client, argv, &optind)) {
                        return -1;
                }

                key = argv[optind++];
                value = argv[optind++];

                if (args_left >= 3) {
                        type = argv[optind++];
                } else {
                        type = "";
                }

                if (jack_set_property (client, uuid, key, value, type)) {
                        fprintf (stderr, "cannot set value for key %s of %s\n", key, subject);
                        exit (1);
                }

        } else {

                /* list properties */

                int args_left = argc - optind;

                if (args_left >= 2) {

                        /* list properties for a UUID/key pair */

                        if (get_subject (client, argv, &optind)) {
                                return -1;
                        }

                        key = argv[optind++];

                        if (jack_get_property (uuid, key, &value, &type) == 0) {
                                printf ("%s\n", value);
                                free (value);
                                if (type) {
                                        free (type);
                                }
                        } else {
                                fprintf (stderr, "Value not found for %s of %s\n", key, subject);
                                exit (1);
                        }

                } else if (args_left == 1) {

                        /* list all properties for a given UUID */

                        jack_description_t description;
                        int cnt, n;

                        if (get_subject (client, argv, &optind)) {
                                return -1;
                        }

                        if ((cnt = jack_get_properties (uuid, &description)) < 0) {
                                fprintf (stderr, "could not retrieve properties for %s\n", subject);
                                exit (1);
                        }

                        for (n = 0; n < cnt; ++n) {
                                if (description.properties[n].type) {
                                        printf ("key: %s value: %s type: %s\n",
                                                description.properties[n].key,
                                                description.properties[n].data,
                                                description.properties[n].type);
                                } else {
                                        printf ("key: %s value: %s\n",
                                                description.properties[n].key,
                                                description.properties[n].data);
                                }
                        }

                        jack_free_description (&description, 0);

                } else {

                        /* list all properties */

                        jack_description_t* description;
                        int cnt, n;
                        size_t p;
                        char buf[JACK_UUID_STRING_SIZE];

                        if ((cnt = jack_get_all_properties (&description)) < 0) {
                                fprintf (stderr, "could not retrieve all properties\n");
                                exit (1);
                        }

                        for (n = 0; n < cnt; ++n) {
                                jack_uuid_unparse (description[n].subject, buf);
                                printf ("%s\n", buf);
                                for (p = 0; p < description[n].property_cnt; ++p) {
                                        if (description[n].properties[p].type) {
                                                printf ("key: %s value: %s type: %s\n",
                                                        description[n].properties[p].key,
                                                        description[n].properties[p].data,
                                                        description[n].properties[p].type);
                                        } else {
                                                printf ("key: %s value: %s\n",
                                                        description[n].properties[p].key,
                                                        description[n].properties[p].data);
                                        }
                                }
                                jack_free_description (&description[n], 0);
                        }

                        free (description);
                }
        }


        (void) jack_client_close (client);
        return 0;
}
