/* Copyright 2003 Tematic Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* mouse interface */
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

#include "Global/RISCOS.h"
#include "Global/Keyboard.h"
#include "Global/Pointer.h"

#include "usbmodhead.h"
#include "swis.h"
#include "debuglib/debuglib.h"
#include "callx/callx.h"

#include <sys/callout.h>
#include <sys/ioctl.h>

#include "dev/usb/usb.h"
#include "dev/usb/usbhid.h"

#include "dev/usb/usbdi.h"
#include "dev/usb/usbdi_util.h"
#include "dev/usb/usbdivar.h"
#include <dev/usb/usbdevs.h>
#include <dev/usb/usb_quirks.h>
#include <dev/usb/hid.h>

#include "usbmouse.h"

#include "wimplib.h"

extern void ums_enable (void*);
extern void ums_disable (void*);

extern int umsdebug = 0;

extern struct messages mod_messages;

extern struct cfattach ums_ca;

static int relx = 0, rely = 0, relz = 0;
static bool enabled = false;

#define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE)
#define MOUSE_FLAGS (HIO_RELATIVE)

void ums_intr (usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status);

struct ums_softc {
        USBBASEDEVICE sc_dev;           /* base device */
        usbd_device_handle sc_udev;
        usbd_interface_handle sc_iface; /* interface */
        usbd_pipe_handle sc_intrpipe;   /* interrupt pipe */
        int sc_ep_addr;

        u_char *sc_ibuf;
        u_int8_t sc_iid;
        int sc_isize;
        struct hid_location sc_loc_x, sc_loc_y, sc_loc_z;
        struct hid_location *sc_loc_btn;

        int sc_enabled;

        int flags;              /* device configuration */
#define UMS_Z           0x01    /* z direction available */
#define UMS_SPUR_BUT_UP 0x02    /* spurious button up events */
#define UMS_REVZ        0x04    /* Z-axis is reversed */

        int nbuttons;
#define MAX_BUTTONS     31      /* chosen because sc_buttons is u_int32_t */

        u_int32_t sc_buttons;   /* mouse button status */

        char                    sc_dying;

        /* list of ukbd softcs */
        TAILQ_ENTRY(ums_softc) link_ms;
};

TAILQ_HEAD(umslist, ums_softc) allums = TAILQ_HEAD_INITIALIZER(allums);

extern void remove_all_mice (void)
{
    struct ums_softc* sc;
    TAILQ_FOREACH(sc, &allums, link_ms)
    {
        detach_mouse ((struct device*) sc);
    }
}

static int match_mouse (struct usb_attach_arg *uaa)
{
        usb_interface_descriptor_t *id;
        int size, ret;
        void *desc;
        usbd_status err;

        dprintf (("", "Trying ums attach\n"));
        if (uaa->iface == NULL)
                return (UMATCH_NONE);
        id = usbd_get_interface_descriptor(uaa->iface);
        if (id == NULL || id->bInterfaceClass != UICLASS_HID)
        {
                dprintf (("", "failed class match: id == %p\n", id));
                return (UMATCH_NONE);
        }

        err = usbd_read_report_desc(uaa->iface, &desc, &size, M_USBDEV);
        if (err)
        {
                dprintf (("", "failed to get report\n"));
                return (UMATCH_NONE);
        }

        if (hid_is_collection(desc, size, 0,
                              HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)))
                ret = UMATCH_IFACECLASS;
        else
                ret = UMATCH_NONE;

        free(desc);
        dprintf (("", "ums attach returning: %d\n", ret));
        return (ret);
}

static void do_attach_mouse (struct ums_softc* sc, struct usb_attach_arg *uaa)
{
        usbd_interface_handle iface = uaa->iface;
        usb_interface_descriptor_t *id;
        usb_endpoint_descriptor_t *ed;
        int size;
        void *desc;
        usbd_status err;
        char devinfo[1024];
        u_int32_t flags, quirks;
        int i, wheel;
        struct hid_location loc_btn;

        sc->sc_udev = uaa->device;
        sc->sc_iface = iface;
        id = usbd_get_interface_descriptor(iface);
        usbd_devinfo(uaa->device, 0, devinfo);
        USB_ATTACH_SETUP;
        dprintf(("%s: %s, iclass %d/%d\n", USBDEVNAME(sc->sc_dev),
               devinfo, id->bInterfaceClass, id->bInterfaceSubClass));
        ed = usbd_interface2endpoint_descriptor(iface, 0);
        if (ed == NULL) {
                logprintf("%s: could not read endpoint descriptor\n",
                       USBDEVNAME(sc->sc_dev));
                USB_ATTACH_ERROR_RETURN;
        }

        dprintf(("", "ums_attach: bLength=%d bDescriptorType=%d "
                     "bEndpointAddress=%d-%s bmAttributes=%d wMaxPacketSize=%d"
                     " bInterval=%d\n",
                     ed->bLength, ed->bDescriptorType,
                     ed->bEndpointAddress & UE_ADDR,
                     UE_GET_DIR(ed->bEndpointAddress)==UE_DIR_IN? "in" : "out",
                     ed->bmAttributes & UE_XFERTYPE,
                     UGETW(ed->wMaxPacketSize), ed->bInterval));

        if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN ||
            (ed->bmAttributes & UE_XFERTYPE) != UE_INTERRUPT) {
                logprintf("%s: unexpected endpoint\n",
                       USBDEVNAME(sc->sc_dev));
                USB_ATTACH_ERROR_RETURN;
        }

        quirks = usbd_get_quirks(uaa->device)->uq_flags;
        if (quirks & UQ_MS_REVZ)
                sc->flags |= UMS_REVZ;
        if (quirks & UQ_SPUR_BUT_UP)
                sc->flags |= UMS_SPUR_BUT_UP;

        err = usbd_read_report_desc(uaa->iface, &desc, &size, M_USBDEV);
        if (err)
                USB_ATTACH_ERROR_RETURN;

        if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
                       0, hid_input, &sc->sc_loc_x, &flags)) {
                logprintf("%s: mouse has no X report\n", USBDEVNAME(sc->sc_dev));
                USB_ATTACH_ERROR_RETURN;
        }
        if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
                logprintf("%s: X report 0x%04x not supported\n",
                       USBDEVNAME(sc->sc_dev), flags);
                USB_ATTACH_ERROR_RETURN;
        }

        if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
                       0, hid_input, &sc->sc_loc_y, &flags)) {
                logprintf("%s: mouse has no Y report\n", USBDEVNAME(sc->sc_dev));
                USB_ATTACH_ERROR_RETURN;
        }
        if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
                logprintf("%s: Y report 0x%04x not supported\n",
                       USBDEVNAME(sc->sc_dev), flags);
                USB_ATTACH_ERROR_RETURN;
        }

        /* Try to guess the Z activator: first check Z, then WHEEL. */
        wheel = 0;
        if (hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z),
                       0, hid_input, &sc->sc_loc_z, &flags) ||
            (wheel = hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP,
                                                       HUG_WHEEL),
                       0, hid_input, &sc->sc_loc_z, &flags))) {
                if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
                        sc->sc_loc_z.size = 0;  /* Bad Z coord, ignore it */
                } else {
                        sc->flags |= UMS_Z;
                        /* Wheels need the Z axis reversed. */
                        if (wheel)
                                sc->flags ^= UMS_REVZ;
                }
        }

        /* figure out the number of buttons */
        for (i = 1; i <= MAX_BUTTONS; i++)
                if (!hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i),
                                0, hid_input, &loc_btn, 0))
                        break;
        sc->nbuttons = i - 1;
        sc->sc_loc_btn = malloc(sizeof(struct hid_location)*sc->nbuttons);
        if (!sc->sc_loc_btn) {
                logprintf("%s: no memory\n", USBDEVNAME(sc->sc_dev));
                USB_ATTACH_ERROR_RETURN;
        }

        dprintf(("", "%s: %d buttons%s\n", USBDEVNAME(sc->sc_dev),
               sc->nbuttons, sc->flags & UMS_Z ? " and Z dir." : ""));

        for (i = 1; i <= sc->nbuttons; i++)
                hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i),
                                0, hid_input, &sc->sc_loc_btn[i-1], 0);

        sc->sc_isize = hid_report_size(desc, size, hid_input, 0);
        sc->sc_ibuf = malloc(sc->sc_isize);
        if (sc->sc_ibuf == NULL) {
                logprintf("%s: no memory\n", USBDEVNAME(sc->sc_dev));
                free(sc->sc_loc_btn);
                USB_ATTACH_ERROR_RETURN;
        }

        sc->sc_ep_addr = ed->bEndpointAddress;
        free(desc);

#ifdef USB_DEBUG
        dprintf(("", "ums_attach: sc=%p\n", sc));
        dprintf(("", "ums_attach: X\t%d/%d\n",
                 sc->sc_loc_x.pos, sc->sc_loc_x.size));
        dprintf(("", "ums_attach: Y\t%d/%d\n",
                 sc->sc_loc_x.pos, sc->sc_loc_x.size));
        if (sc->flags & UMS_Z)
                dprintf(("", "ums_attach: Z\t%d/%d\n",
                         sc->sc_loc_z.pos, sc->sc_loc_z.size));
        for (i = 1; i <= sc->nbuttons; i++) {
                dprintf(("", "ums_attach: B%d\t%d/%d\n",
                         i, sc->sc_loc_btn[i-1].pos,sc->sc_loc_btn[i-1].size));
        }
        dprintf(("", "ums_attach: size=%d, id=%d\n", sc->sc_isize, sc->sc_iid));
#endif

        usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev,
                           USBDEV(sc->sc_dev));

        USB_ATTACH_SUCCESS_RETURN;
}

struct device* attach_mouse (struct device* parent, void* aux)
{
    struct device* softc;
    struct ums_softc* sc;

    dprintf (("", "Trying match on usb mouse\n"));

    /* First see if we match */
    if (match_mouse (aux) == UMATCH_NONE)
    {
        dprintf (("", "Failed to match\n"));
        return NULL;
    }

    /* If so, allocate memory for the device and attach ourselves. */
    softc = malloc (sizeof *sc);
    if (softc == 0) {
        dprintf (("", "Couldn't allocate memory for mouse device\n"));
        return NULL;
    }
    memset (softc, 0, sizeof *sc);
    strcpy (softc->dv_xname, "USBMouse"Module_VersionString);
    softc->dv_cfdata = (void*) 3; // mouse

    /* enable */
    sc = (struct ums_softc*) softc;

    do_attach_mouse (sc, aux);
    dprintf (("", "Matched mouse\n"));


    sc->sc_enabled = 1;
    sc->sc_buttons = 0;

    /* set idle rate to 0 */
    usbd_set_idle (sc->sc_iface, 0, 0);

    int err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_ep_addr,
          USBD_SHORT_XFER_OK, &sc->sc_intrpipe, sc,
          sc->sc_ibuf, sc->sc_isize, ums_intr, USBD_DEFAULT_INTERVAL);

    if (err) {
            dprintf(("", "ums_enable: usbd_open_pipe_intr failed, error=%d\n",
                     err));
            sc->sc_enabled = 0;
    }
    else
        _swix (OS_Pointer, _INR(0,1), 1, PointerDevice_USB);

    TAILQ_INSERT_TAIL (&allums, sc, link_ms);

    return softc;
}

int detach_mouse (struct device* ms)
{
    struct ums_softc* sc= (struct ums_softc*) ms;
    int rv;
    usbd_abort_pipe(sc->sc_intrpipe);
    usbd_close_pipe(sc->sc_intrpipe);

    dprintf (("", "detaching mouse, buttons = %x", sc->sc_buttons));

    if (sc->sc_buttons & 1) {
        dprintf (("", "releaseing 1\n"));
        _swix (OS_CallAVector, _INR(0,1) | _IN(9),
            KeyV_KeyUp, KeyNo_LeftMouse, KEYV);
    }
    if (sc->sc_buttons & 2)
    {
        dprintf (("", "releaseing 2\n"));
        _swix (OS_CallAVector, _INR(0,1) | _IN(9),
        KeyV_KeyUp, KeyNo_RightMouse, KEYV);
    }
    if (sc->sc_buttons & 4)
    {
        dprintf (("", "releaseing 4\n"));
        _swix (OS_CallAVector, _INR(0,1) | _IN(9),
            KeyV_KeyUp, KeyNo_CentreMouse, KEYV);
    }

    TAILQ_REMOVE (&allums, sc, link_ms);

    if (rv == 0) {
        free(sc->sc_loc_btn);
        free(sc->sc_ibuf);
    }
    free (ms);
    return 0;
}

int pointerv (_kernel_swi_regs* r, void* pw)
{
    _kernel_oserror* e;
    (void) pw;

    switch (r->r[0]) {
    case PointerReason_Request:
        if (r->r[1] == PointerDevice_USB) {
            /* Turn off interrupts while updating */
            _kernel_irqs_off ();
            r->r[2] = relx;
            r->r[3] = rely;
            relx = rely = 0;
            _kernel_irqs_on ();
            }
        break;

#define RECORD "\x0\x0\x0\x0\x0\x0\x0\x0\x7USB Mouse"
    case PointerReason_Identify:
    {
        struct pointer_device {
            struct pointer_device *next;
            uint32_t               flags;
            char                   typenname[32];
        } *p;
        e = _swix (OS_Module, _IN(0) | _IN(3) | _OUT(2), 6,
            sizeof *p, &p);

        if (!e) {
            p->next = (struct pointer_device *) r->r[1];
            p->flags = 0;
            p->typenname[0] = PointerDevice_USB;

            _swix (MessageTrans_Lookup, _INR(0,3),
                   &mod_messages, "Mouse:USB mouse", &p->typenname[1], 31);
            r->r[1] = (int) p;
        }
        break;
    }

    case PointerReason_Selected:
        if (r->r[1] == PointerDevice_USB) {
            dprintf (("", "USB mouse enabled\n"));
            enabled = true;
        } else {
            dprintf (("", "USB mouse disabled\n"));
            enabled = false;
        }
        break;
    }

    return 1;
}

static _kernel_oserror *zscroll_handler
(
    _kernel_swi_regs *  r,
    void *              pw,
    void *              h
)
{
    (void) r;
    (void) pw;
    (void) h;

    while (relz != 0)
    {
        int b[64];
        _swix (Wimp_GetPointerInfo, _IN(1), b);
        b[0] = b[3];
        _swix (Wimp_GetWindowState, _IN(1), b);
        b[8] = 0;
        b[9] = relz > 0? -1: 1;
        relz += relz > 0? -1: 1;
        _swix (Wimp_SendMessage, _INR(0,2), 10, b, b[0]);
    }

    return NULL;
}

void ums_intr
(
    usbd_xfer_handle    xfer,
    usbd_private_handle addr,
    usbd_status         status
)
{
    struct ums_softc *sc = addr;
    u_char *ibuf = sc->sc_ibuf;
    int dx, dy, dz, i, b = 0;
    uint8_t change;

    if (status == USBD_CANCELLED)
        return;

    if (status) {
        dprintf(("", "ums_intr: status=%d\n", status));
        usbd_clear_endpoint_stall_async(sc->sc_intrpipe);
        return;
    }

    dx = hid_get_data(ibuf, &sc->sc_loc_x);
    dy = -hid_get_data(ibuf, &sc->sc_loc_y);
    dz =  hid_get_data(ibuf, &sc->sc_loc_z);
    if (sc->flags & UMS_REVZ)
        dz = -dz;
    relx += dx;
    rely += dy;
    relz += 5 * dz; /* 5 makes things move a bit faster */
    if (enabled)
    {
        _swix(OS_CallAVector, _INR(0,3) | _IN(9),
                          PointerReason_Report, PointerDevice_USB,
                          dx, dy,
                          PointerV);
        if (relz != 0)
        {
            callx_add_callback (zscroll_handler, 0);
        }
    }

    for (i = 0; i < sc->nbuttons; i++)
        if (hid_get_data(ibuf, &sc->sc_loc_btn[i]))
            b |= (1 << i);

#ifdef USB_DEBUG
    if (umsdebug > 5) dprintf(("",
    "data.relx = %d, data.rely = %d, relx = %d, rely = %d, buttons = %x\n",
        dx, dy, relx, rely, b));
#endif
    if ((change = sc->sc_buttons ^ b) != 0) {
        sc->sc_buttons = b;
#ifdef USB_DEBUG
        if (umsdebug > 5) dprintf (("", "change = %x, enabled = %d\n", change,
            enabled));
#endif
        if (enabled) {
            if (change & 1) _swix (OS_CallAVector, _INR(0,1) | _IN(9),
                (b & 1)? KeyV_KeyDown: KeyV_KeyUp, KeyNo_LeftMouse, KEYV);

            if (change & 2) _swix (OS_CallAVector, _INR(0,1) | _IN(9),
                (b & 2)? KeyV_KeyDown: KeyV_KeyUp, KeyNo_RightMouse, KEYV);

            if (change & 4) _swix (OS_CallAVector, _INR(0,1) | _IN(9),
                (b & 4)? KeyV_KeyDown: KeyV_KeyUp, KeyNo_CentreMouse, KEYV);
        }
    }
}

int wsmousedevprint (void * v, const char * c)
{
    (void) v;
    (void) c;
    return 0;
}