/*
 * Copyright © 2007 Peter Hutterer
 * Copyright © 2009 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <X11/Xatom.h>
#include <X11/extensions/XIproto.h>

#include "xinput.h"

static Atom parse_atom(Display *dpy, char *name) {
    Bool is_atom = True;
    int i;

    for (i = 0; name[i] != '\0'; i++) {
        if (!isdigit(name[i])) {
            is_atom = False;
            break;
        }
    }

    if (is_atom)
        return atoi(name);
    else
        return XInternAtom(dpy, name, False);
}

static void
print_property(Display *dpy, XDevice* dev, Atom property)
{
    Atom                act_type;
    char                *name;
    int                 act_format;
    unsigned long       nitems, bytes_after;
    unsigned char       *data, *ptr;
    int                 j, done = False, size = 0;

    name = XGetAtomName(dpy, property);
    printf("\t%s (%ld):\t", name, property);
    XFree(name);

    if (XGetDeviceProperty(dpy, dev, property, 0, 1000, False,
                           AnyPropertyType, &act_type, &act_format,
                           &nitems, &bytes_after, &data) == Success)
    {
        Atom float_atom = XInternAtom(dpy, "FLOAT", True);

        ptr = data;

        if (nitems == 0)
            printf("<no items>");

        switch(act_format)
        {
            case 8: size = sizeof(char); break;
            case 16: size = sizeof(short); break;
            case 32: size = sizeof(long); break;
        }

        for (j = 0; j < nitems; j++)
        {
            switch(act_type)
            {
                case XA_INTEGER:
                    switch(act_format)
                    {
                        case 8:
                            printf("%d", *((char*)ptr));
                            break;
                        case 16:
                            printf("%d", *((short*)ptr));
                            break;
                        case 32:
                            printf("%ld", *((long*)ptr));
                            break;
                    }
                    break;
                case XA_CARDINAL:
                    switch(act_format)
                    {
                        case 8:
                            printf("%u", *((unsigned char*)ptr));
                            break;
                        case 16:
                            printf("%u", *((unsigned short*)ptr));
                            break;
                        case 32:
                            printf("%lu", *((unsigned long*)ptr));
                            break;
                    }
                    break;
                case XA_STRING:
                    if (act_format != 8)
                    {
                        printf("Unknown string format.\n");
                        done = True;
                        break;
                    }
                    printf("\"%s\"", ptr);
                    j += strlen((char*)ptr); /* The loop's j++ jumps over the
                                                terminating 0 */
                    ptr += strlen((char*)ptr); /* ptr += size below jumps over
                                                  the terminating 0 */
                    break;
                case XA_ATOM:
                    {
                        Atom a = *(Atom*)ptr;
                        name = (a) ? XGetAtomName(dpy, a) : NULL;
                        printf("\"%s\" (%d)", name ? name : "None", (int)a);
                        XFree(name);
                        break;
                    }
                default:
                    if (float_atom != None && act_type == float_atom)
                    {
                        printf("%f", *((float*)ptr));
                        break;
                    }

                    name = XGetAtomName(dpy, act_type);
                    printf("\t... of unknown type '%s'\n", name);
                    XFree(name);
                    done = True;
                    break;
            }

            ptr += size;

            if (done == True)
                break;
            if (j < nitems - 1)
                printf(", ");
        }
        printf("\n");
        XFree(data);
    } else
        printf("\tFetch failure\n");

}

static int
list_props_xi1(Display *dpy, int argc, char** argv, char* name, char *desc)
{
    XDeviceInfo *info;
    XDevice     *dev;
    int          i;
    int         nprops;
    Atom        *props;
    int         rc = EXIT_SUCCESS;

    if (argc == 0)
    {
        fprintf(stderr, "Usage: xinput %s %s\n", name, desc);
        return EXIT_FAILURE;
    }

    for (i = 0; i < argc; i++)
    {
        info = find_device_info(dpy, argv[i], False);
        if (!info)
        {
            fprintf(stderr, "unable to find device '%s'\n", argv[i]);
            rc = EXIT_FAILURE;
            continue;
        }

        dev = XOpenDevice(dpy, info->id);
        if (!dev)
        {
            fprintf(stderr, "unable to open device '%s'\n", info->name);
            rc = EXIT_FAILURE;
            continue;
        }

        props = XListDeviceProperties(dpy, dev, &nprops);
        if (!nprops)
        {
            printf("Device '%s' does not report any properties.\n", info->name);
            continue;
        }

        printf("Device '%s':\n", info->name);
        while(nprops--)
        {
            print_property(dpy, dev, props[nprops]);
        }

        XFree(props);
        XCloseDevice(dpy, dev);
    }
    return rc;
}


int watch_props(Display *dpy, int argc, char** argv, char* n, char *desc)
{
    XDevice     *dev;
    XDeviceInfo *info;
    XEvent      ev;
    XDevicePropertyNotifyEvent *dpev;
    char        *name;
    int         type_prop;
    XEventClass cls_prop;

    if (list_props(dpy, argc, argv, n, desc) != EXIT_SUCCESS)
        return EXIT_FAILURE;

    info = find_device_info(dpy, argv[0], False);
    if (!info)
    {
        fprintf(stderr, "unable to find device '%s'\n", argv[0]);
        return EXIT_FAILURE;
    }

    dev = XOpenDevice(dpy, info->id);
    if (!dev)
    {
        fprintf(stderr, "unable to open device '%s'\n", info->name);
        return EXIT_FAILURE;
    }

    DevicePropertyNotify(dev, type_prop, cls_prop);
    XSelectExtensionEvent(dpy, DefaultRootWindow(dpy), &cls_prop, 1);

    while(1)
    {
        XNextEvent(dpy, &ev);

        dpev = (XDevicePropertyNotifyEvent*)&ev;
        if (dpev->type != type_prop)
            continue;

        name = XGetAtomName(dpy, dpev->atom);
        printf("Property '%s' changed.\n", name);
        XFree(name);
        print_property(dpy, dev, dpev->atom);
    }

    XCloseDevice(dpy, dev);
}

static int
delete_prop_xi1(Display *dpy, int argc, char** argv, char* n, char *desc)
{
    XDevice     *dev;
    XDeviceInfo *info;
    char        *name;
    Atom        prop;

    info = find_device_info(dpy, argv[0], False);
    if (!info)
    {
        fprintf(stderr, "unable to find device '%s'\n", argv[0]);
        return EXIT_FAILURE;
    }

    dev = XOpenDevice(dpy, info->id);
    if (!dev)
    {
        fprintf(stderr, "unable to open device '%s'\n", info->name);
        return EXIT_FAILURE;
    }

    name = argv[1];

    prop = parse_atom(dpy, name);

    XDeleteDeviceProperty(dpy, dev, prop);

    XCloseDevice(dpy, dev);
    return EXIT_SUCCESS;
}

static int
do_set_prop_xi1(Display *dpy, Atom type, int format, int argc, char **argv, char *n, char *desc)
{
    XDeviceInfo  *info;
    XDevice      *dev;
    Atom          prop;
    Atom          old_type;
    char         *name;
    int           i;
    Atom          float_atom;
    int           old_format, nelements = 0;
    unsigned long act_nitems, bytes_after;
    char         *endptr;
    union {
        unsigned char *c;
        short *s;
        long *l;
        Atom *a;
    } data;

    if (argc < 3)
    {
        fprintf(stderr, "Usage: xinput %s %s\n", n, desc);
        return EXIT_FAILURE;
    }

    info = find_device_info(dpy, argv[0], False);
    if (!info)
    {
        fprintf(stderr, "unable to find device '%s'\n", argv[0]);
        return EXIT_FAILURE;
    }

    dev = XOpenDevice(dpy, info->id);
    if (!dev)
    {
        fprintf(stderr, "unable to open device '%s'\n", argv[0]);
        return EXIT_FAILURE;
    }

    name = argv[1];

    prop = parse_atom(dpy, name);

    if (prop == None) {
        fprintf(stderr, "invalid property '%s'\n", name);
        return EXIT_FAILURE;
    }

    float_atom = XInternAtom(dpy, "FLOAT", False);

    nelements = argc - 2;
    if (type == None || format == 0) {
        if (XGetDeviceProperty(dpy, dev, prop, 0, 0, False, AnyPropertyType,
                               &old_type, &old_format, &act_nitems,
                               &bytes_after, &data.c) != Success) {
            fprintf(stderr, "failed to get property type and format for '%s'\n",
                    name);
            return EXIT_FAILURE;
        } else {
            if (type == None)
                type = old_type;
            if (format == 0)
                format = old_format;
        }

        XFree(data.c);
    }

    if (type == None) {
        fprintf(stderr, "property '%s' doesn't exist, you need to specify "
                "its type and format\n", name);
        return EXIT_FAILURE;
    }

    data.c = calloc(nelements, sizeof(long));

    for (i = 0; i < nelements; i++)
    {
        if (type == XA_INTEGER || type == XA_CARDINAL) {
            switch (format)
            {
                case 8:
                    data.c[i] = atoi(argv[2 + i]);
                    break;
                case 16:
                    data.s[i] = atoi(argv[2 + i]);
                    break;
                case 32:
                    data.l[i] = atoi(argv[2 + i]);
                    break;
                default:
                    fprintf(stderr, "unexpected size for property '%s'", name);
                    return EXIT_FAILURE;
            }
        } else if (type == float_atom) {
            if (format != 32) {
                fprintf(stderr, "unexpected format %d for property '%s'\n",
                        format, name);
                return EXIT_FAILURE;
            }
            *(float *)(data.l + i) = strtod(argv[2 + i], &endptr);
            if (endptr == argv[2 + i]) {
                fprintf(stderr, "argument '%s' could not be parsed\n", argv[2 + i]);
                return EXIT_FAILURE;
            }
        } else if (type == XA_ATOM) {
            if (format != 32) {
                fprintf(stderr, "unexpected format %d for property '%s'\n",
                        format, name);
                return EXIT_FAILURE;
            }
            data.a[i] = parse_atom(dpy, argv[2 + i]);
        } else {
            fprintf(stderr, "unexpected type for property '%s'\n", name);
            return EXIT_FAILURE;
        }
    }

    XChangeDeviceProperty(dpy, dev, prop, type, format, PropModeReplace,
                          data.c, nelements);
    free(data.c);
    XCloseDevice(dpy, dev);
    return EXIT_SUCCESS;
}

#if HAVE_XI2
static void
print_property_xi2(Display *dpy, int deviceid, Atom property)
{
    Atom                act_type;
    char                *name;
    int                 act_format;
    unsigned long       nitems, bytes_after;
    unsigned char       *data, *ptr;
    int                 j, done = False;

    name = XGetAtomName(dpy, property);
    printf("\t%s (%ld):\t", name, property);
    XFree(name);

    if (XIGetProperty(dpy, deviceid, property, 0, 1000, False,
                           AnyPropertyType, &act_type, &act_format,
                           &nitems, &bytes_after, &data) == Success)
    {
        Atom float_atom = XInternAtom(dpy, "FLOAT", True);

        ptr = data;

        if (nitems == 0)
            printf("<no items>");

        for (j = 0; j < nitems; j++)
        {
            switch(act_type)
            {
                case XA_INTEGER:
                    switch(act_format)
                    {
                        case 8:
                            printf("%d", *((int8_t*)ptr));
                            break;
                        case 16:
                            printf("%d", *((int16_t*)ptr));
                            break;
                        case 32:
                            printf("%d", *((int32_t*)ptr));
                            break;
                    }
                    break;
                case XA_CARDINAL:
                    switch(act_format)
                    {
                        case 8:
                            printf("%u", *((uint8_t*)ptr));
                            break;
                        case 16:
                            printf("%u", *((uint16_t*)ptr));
                            break;
                        case 32:
                            printf("%u", *((uint32_t*)ptr));
                            break;
                    }
                    break;
                case XA_STRING:
                    if (act_format != 8)
                    {
                        printf("Unknown string format.\n");
                        done = True;
                        break;
                    }
                    printf("\"%s\"", ptr);
                    j += strlen((char*)ptr); /* The loop's j++ jumps over the
                                                terminating 0 */
                    ptr += strlen((char*)ptr); /* ptr += size below jumps over
                                                  the terminating 0 */
                    break;
                case XA_ATOM:
                    {
                        Atom a = *(uint32_t*)ptr;
                        name = (a) ? XGetAtomName(dpy, a) : NULL;
                        printf("\"%s\" (%ld)", name ? name : "None", a);
                        XFree(name);
                        break;
                    }
                    break;
                default:
                    if (float_atom != None && act_type == float_atom)
                    {
                        printf("%f", *((float*)ptr));
                        break;
                    }

                    name = XGetAtomName(dpy, act_type);
                    printf("\t... of unknown type %s\n", name);
                    XFree(name);
                    done = True;
                    break;
            }

            ptr += act_format/8;

            if (done == True)
                break;
            if (j < nitems - 1)
                printf(", ");
        }
        printf("\n");
        XFree(data);
    } else
        printf("\tFetch failure\n");

}

static int
list_props_xi2(Display *dpy, int argc, char** argv, char* name, char *desc)
{
    XIDeviceInfo *info;
    int         i;
    int         nprops;
    Atom        *props;
    int         rc = EXIT_SUCCESS;

    if (argc == 0)
    {
        fprintf(stderr, "Usage: xinput %s %s\n", name, desc);
        return EXIT_FAILURE;
    }

    for (i = 0; i < argc; i++)
    {
        info = xi2_find_device_info(dpy, argv[i]);
        if (!info)
        {
            fprintf(stderr, "unable to find device %s\n", argv[i]);
            rc = EXIT_FAILURE;
            continue;
        }

        props = XIListProperties(dpy, info->deviceid, &nprops);
        if (!nprops)
        {
            printf("Device '%s' does not report any properties.\n", info->name);
            continue;
        }

        printf("Device '%s':\n", info->name);
        while(nprops--)
        {
            print_property_xi2(dpy, info->deviceid, props[nprops]);
        }

        XFree(props);
    }
    return rc;
}

static int
delete_prop_xi2(Display *dpy, int argc, char** argv, char* n, char *desc)
{
    XIDeviceInfo *info;
    char        *name;
    Atom        prop;

    info = xi2_find_device_info(dpy, argv[0]);
    if (!info)
    {
        fprintf(stderr, "unable to find device %s\n", argv[0]);
        return EXIT_FAILURE;
    }

    name = argv[1];

    prop = parse_atom(dpy, name);

    XIDeleteProperty(dpy, info->deviceid, prop);

    return EXIT_SUCCESS;
}

static int
do_set_prop_xi2(Display *dpy, Atom type, int format, int argc, char **argv, char *n, char *desc)
{
    XIDeviceInfo *info;
    Atom          prop;
    Atom          old_type;
    char         *name;
    int           i;
    Atom          float_atom;
    int           old_format, nelements = 0;
    unsigned long act_nitems, bytes_after;
    char         *endptr;
    union {
        unsigned char *c;
        int16_t *s;
        int32_t *l;
    } data = { NULL };
    int rc = EXIT_FAILURE;

    if (argc < 3)
    {
        fprintf(stderr, "Usage: xinput %s %s\n", n, desc);
        goto out;
    }

    info = xi2_find_device_info(dpy, argv[0]);
    if (!info)
    {
        fprintf(stderr, "unable to find device %s\n", argv[0]);
        goto out;
    }

    name = argv[1];

    prop = parse_atom(dpy, name);

    if (prop == None) {
        fprintf(stderr, "invalid property '%s'\n", name);
        goto out;
    }

    float_atom = XInternAtom(dpy, "FLOAT", False);

    nelements = argc - 2;
    if (type == None || format == 0) {
        if (XIGetProperty(dpy, info->deviceid, prop, 0, 0, False,
                          AnyPropertyType, &old_type, &old_format, &act_nitems,
                          &bytes_after, &data.c) != Success) {
            fprintf(stderr, "failed to get property type and format for '%s'\n",
                    name);
            goto out;
        } else {
            if (type == None)
                type = old_type;
            if (format == 0)
                format = old_format;
        }

        XFree(data.c);
    }

    if (type == None) {
        fprintf(stderr, "property '%s' doesn't exist, you need to specify "
                "its type and format\n", name);
        goto out;
    }

    data.c = calloc(nelements, sizeof(int32_t));

    for (i = 0; i < nelements; i++)
    {
        if (type == XA_INTEGER || type == XA_CARDINAL) {
            switch (format)
            {
                case 8:
                    data.c[i] = atoi(argv[2 + i]);
                    break;
                case 16:
                    data.s[i] = atoi(argv[2 + i]);
                    break;
                case 32:
                    data.l[i] = atoi(argv[2 + i]);
                    break;
                default:
                    fprintf(stderr, "unexpected size for property %s", name);
                    goto out;
            }
        } else if (type == float_atom) {
            if (format != 32) {
                fprintf(stderr, "unexpected format %d for property '%s'\n",
                        format, name);
                goto out;
            }
            *(float *)(data.l + i) = strtod(argv[2 + i], &endptr);
            if (endptr == argv[2 + i]) {
                fprintf(stderr, "argument %s could not be parsed\n", argv[2 + i]);
                goto out;
            }
        } else if (type == XA_ATOM) {
            if (format != 32) {
                fprintf(stderr, "unexpected format %d for property '%s'\n",
                        format, name);
                goto out;
            }
            data.l[i] = parse_atom(dpy, argv[2 + i]);
        } else {
            fprintf(stderr, "unexpected type for property '%s'\n", name);
            goto out;
        }
    }

    XIChangeProperty(dpy, info->deviceid, prop, type, format, PropModeReplace,
                          data.c, nelements);
    rc = EXIT_SUCCESS;
out:
    free(data.c);
    return rc;
}
#endif

int list_props(Display *display, int argc, char *argv[], char *name,
               char *desc)
{
#if HAVE_XI2
    if (xinput_version(display) == XI_2_Major)
        return list_props_xi2(display, argc, argv, name, desc);
#endif
    return list_props_xi1(display, argc, argv, name, desc);

}

int delete_prop(Display *display, int argc, char *argv[], char *name,
                char *desc)
{
#if HAVE_XI2
    if (xinput_version(display) == XI_2_Major)
        return delete_prop_xi2(display, argc, argv, name, desc);
#endif
    return delete_prop_xi1(display, argc, argv, name, desc);

}

static int
do_set_prop(Display *display, Atom type, int format, int argc, char *argv[], char *name, char *desc)
{
#if HAVE_XI2
    if (xinput_version(display) == XI_2_Major)
        return do_set_prop_xi2(display, type, format, argc, argv, name, desc);
#endif
    return do_set_prop_xi1(display, type, format, argc, argv, name, desc);
}

int
set_atom_prop(Display *dpy, int argc, char** argv, char* n, char *desc)
{
    return do_set_prop(dpy, XA_ATOM, 32, argc, argv, n, desc);
}

int
set_int_prop(Display *dpy, int argc, char** argv, char* n, char *desc)
{
    int          i;
    int          format;

    if (argc < 3)
    {
        fprintf(stderr, "Usage: xinput %s %s\n", n, desc);
        return EXIT_FAILURE;
    }

    format    = atoi(argv[2]);
    if (format != 8 && format != 16 && format != 32)
    {
        fprintf(stderr, "Invalid format %d\n", format);
        return EXIT_FAILURE;
    }

    for (i = 3; i < argc; i++)
        argv[i - 1] = argv[i];

    return do_set_prop(dpy, XA_INTEGER, format, argc - 1, argv, n, desc);
}

int
set_float_prop(Display *dpy, int argc, char** argv, char* n, char *desc)
{
    Atom float_atom = XInternAtom(dpy, "FLOAT", False);

    if (sizeof(float) != 4)
    {
	fprintf(stderr, "sane FP required\n");
	return EXIT_FAILURE;
    }

    return do_set_prop(dpy, float_atom, 32, argc, argv, n, desc);
}

int set_prop(Display *display, int argc, char *argv[], char *name,
             char *desc)
{
    Atom type = None;
    int format = 0;
    int i = 0, j;

    while (i < argc) {
        char *option = strchr(argv[i], '=');
        /* skip non-option arguments */
        if (strncmp(argv[i], "--", 2) || !option) {
            i++;
            continue;
        }

        if (!strncmp(argv[i], "--type=", strlen("--type="))) {
            if (!strcmp(option + 1, "int")) {
                type = XA_INTEGER;
            } else if (!strcmp(option + 1, "float")) {
                type = XInternAtom(display, "FLOAT", False);
                format = 32;
            } else if (!strcmp(option + 1, "atom")) {
                type = XA_ATOM;
                format = 32;
            } else {
                fprintf(stderr, "unknown property type %s\n", option + 1);
                return EXIT_FAILURE;
            }
        } else if (!strncmp(argv[i], "--format=", strlen("--format="))) {
            format = atoi(option + 1);
            if (format != 8 && format != 16 && format != 32) {
                fprintf(stderr, "invalid property format '%s'\n", option + 1);
                return EXIT_FAILURE;
            }
        } else {
            fprintf(stderr, "invalid option '%s'\n", argv[i]);
            return EXIT_FAILURE;
        }

        for (j = i; j + 1 < argc; j++)
            argv[j] = argv[j + 1];
        argc--;
    }

    return do_set_prop(display, type, format, argc, argv, name, desc);
}

int disable(Display *display, int argc, char *argv[], char *name, char *desc)
{
    char *new_argv[3] = { NULL, "Device Enabled", "0" };

    if (argc != 1) {
        fprintf(stderr, "Usage: xinput %s %s\n", name, desc);
        return EXIT_FAILURE;
    }

    new_argv[0] = argv[0];

    return set_prop(display, 3, new_argv, name, desc);
}

int enable(Display *display, int argc, char *argv[], char *name, char *desc)
{
    char *new_argv[3] = { NULL, "Device Enabled", "1" };

    if (argc != 1) {
        fprintf(stderr, "Usage: xinput %s %s\n", name, desc);
        return EXIT_FAILURE;
    }

    new_argv[0] = argv[0];

    return set_prop(display, 3, new_argv, name, desc);
}
