/* 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 "callx/callx.h"

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

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

#include "dev/usb/usb.h"
#include "dev/usb/usbdi.h"
#include "dev/usb/usbdi_util.h"
#include "dev/usb/usbdivar.h"
#include "dev/usb/usbhid.h"

#include "usbkboard.h"

#include "wimplib.h"

#define MagicNoDebounce 0x4e6f4b64

#define NUM_LOCK 0x01
#define CAPS_LOCK 0x02
#define SCROLL_LOCK 0x04

#define RSVD 0xFF     /* Reserved keys that have no mapping */
#define NEQV 0xFF     /* Keys in USB that have no RISC OS equivalent */
#define UDEF 0xFF     /* Keys that are undefined */

#define NKEYCODE 6

extern void* private_word;

void ukbd_intr(usbd_xfer_handle, usbd_private_handle, usbd_status);

struct ukbd_data {
    uint8_t modifiers;
    uint8_t reserved;
    uint8_t keycode[NKEYCODE];
};

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

    /* bits to keep track of keys currently depressed */
    uint32_t                status[8];

    struct ukbd_data        data, odata;

    /* LEDS */
    uint8_t                 res;

    /* list of ukbd softcs */
    TAILQ_ENTRY(ukbd_softc) link_kb;
};

TAILQ_HEAD(ukbdlist, ukbd_softc) allukbds = TAILQ_HEAD_INITIALIZER(allukbds);

/* Mapping table from USB keycodes to low-level internal key numbers - see PRM
 * 1-156.  The index into the table is the USB keycode, as defined in the HID
 * Usage tables.  The array starts off a-z, 1-0.
 */
static unsigned char mapping_table[256] = {
RSVD,               RSVD,            RSVD,             RSVD,             /*0*/
KeyNo_LetterA,      KeyNo_LetterB,   KeyNo_LetterC,    KeyNo_LetterD,
KeyNo_LetterE,      KeyNo_LetterF,   KeyNo_LetterG,    KeyNo_LetterH,
KeyNo_LetterI,      KeyNo_LetterJ,   KeyNo_LetterK,    KeyNo_LetterL,
KeyNo_LetterM,      KeyNo_LetterN,   KeyNo_LetterO,    KeyNo_LetterP,    /*1*/
KeyNo_LetterQ,      KeyNo_LetterR,   KeyNo_LetterS,    KeyNo_LetterT,
KeyNo_LetterU,      KeyNo_LetterV,   KeyNo_LetterW,    KeyNo_LetterX,
KeyNo_LetterY,      KeyNo_LetterZ,   KeyNo_Digit1,     KeyNo_Digit2,
KeyNo_Digit3,       KeyNo_Digit4,    KeyNo_Digit5,     KeyNo_Digit6,     /*2*/
KeyNo_Digit7,       KeyNo_Digit8,    KeyNo_Digit9,     KeyNo_Digit0,
KeyNo_Return,       KeyNo_Escape,    KeyNo_BackSpace,  KeyNo_Tab,
KeyNo_Space,        KeyNo_Minus,     KeyNo_Equals,     KeyNo_OpenSquare,
KeyNo_CloseSquare,  KeyNo_BackSlash, KeyNo_BackSlash,  KeyNo_SemiColon,  /*3*/
KeyNo_Tick,         KeyNo_BackTick,  KeyNo_Comma,      KeyNo_Dot,
KeyNo_Slash,        KeyNo_CapsLock,  KeyNo_Function1,  KeyNo_Function2,
KeyNo_Function3,    KeyNo_Function4, KeyNo_Function5,  KeyNo_Function6,
KeyNo_Function7,    KeyNo_Function8, KeyNo_Function9,  KeyNo_Function10, /*4*/
KeyNo_Function11,   KeyNo_Function12,KeyNo_Print,      KeyNo_ScrollLock,
KeyNo_Break,        KeyNo_Insert,    KeyNo_Home,       KeyNo_PageUp,
KeyNo_Delete,       KeyNo_Copy,      KeyNo_PageDown,   KeyNo_CursorRight,
KeyNo_CursorLeft,   KeyNo_CursorDown,KeyNo_CursorUp,   KeyNo_NumLock,    /*5*/
KeyNo_NumPadSlash,  KeyNo_NumPadStar,KeyNo_NumPadMinus,KeyNo_NumPadPlus,
KeyNo_NumPadEnter,  KeyNo_NumPad1,   KeyNo_NumPad2,    KeyNo_NumPad3,
KeyNo_NumPad4,      KeyNo_NumPad5,   KeyNo_NumPad6,    KeyNo_NumPad7,
KeyNo_NumPad8,      KeyNo_NumPad9,   KeyNo_NumPad0,    KeyNo_NumPadDot,  /*6*/
KeyNo_NotFittedLeft,KeyNo_Menu,      NEQV,             KeyNo_NumPadHash,
NEQV,               NEQV,            NEQV,             NEQV,
NEQV,               NEQV,            NEQV,             NEQV,
NEQV,               NEQV,            NEQV,             NEQV,             /*7*/
NEQV,               NEQV,            NEQV,             NEQV,
NEQV,               NEQV,            NEQV,             NEQV,
NEQV,               NEQV,            NEQV,             NEQV,
NEQV,               NEQV,            NEQV,             NEQV,             /*8*/
NEQV,               NEQV,            NEQV,             KeyNo_NotFittedRight,
KeyNo_Kana,         KeyNo_Pound,     KeyNo_Convert,    KeyNo_NoConvert,
NEQV,               NEQV,            NEQV,             NEQV,
NEQV,               NEQV,            NEQV,             NEQV,             /*9*/
NEQV,               NEQV,            NEQV,             NEQV,
NEQV,               NEQV,            NEQV,             NEQV,
NEQV,               NEQV,            NEQV,             NEQV,
NEQV,               NEQV,            NEQV,             NEQV,             /*a*/
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,             /*b*/
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,             /*c*/
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,             /*d*/
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,
KeyNo_CtrlLeft,     KeyNo_ShiftLeft, KeyNo_AltLeft,    KeyNo_AcornLeft,  /*e*/
KeyNo_CtrlRight,    KeyNo_ShiftRight,KeyNo_AltRight,   KeyNo_AcornRight,
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,             /*f*/
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD,
RSVD,               RSVD,            RSVD,             RSVD
};

extern void remove_all_keyboards (void)
{
    struct ukbd_softc* sc;
    TAILQ_FOREACH(sc, &allukbds, link_kb)
    {
        detach_keyboard ((struct device*) sc);
    }
}

struct device* attach_keyboard (struct device* parent, void* aux)
{
    struct ukbd_softc* softc;
    struct usb_attach_arg *uaa = aux;
    usb_interface_descriptor_t *id;
    usb_endpoint_descriptor_t *ed;

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

    /* First see if we match */
    /* Check that this is a keyboard that speaks the boot protocol. */
    if (uaa->iface == NULL)
    {
       dprintf (("", "Failed to match\n"));
       return (UMATCH_NONE);
    }
    id = usbd_get_interface_descriptor(uaa->iface);
    if (id == NULL ||
        id->bInterfaceClass != UICLASS_HID ||
        id->bInterfaceSubClass != UISUBCLASS_BOOT ||
        id->bInterfaceProtocol != UIPROTO_BOOT_KEYBOARD)
    {
       dprintf (("", "Failed to match\n"));
       return (UMATCH_NONE);
    }

    /* If so, allocate memory for the device and attach ourselves. */
    softc = calloc (sizeof *softc, 1);
    if (softc == 0) {
        dprintf (("", "Couldn't allocate memory for keyboard device\n"));
        return NULL;
    }
    strcpy (softc->sc_dev.dv_xname, "USBK"Module_VersionString);
    softc->sc_dev.dv_cfdata = (void*) 4; // keyboard

    softc->sc_udev = uaa->device;
    softc->sc_iface = uaa->iface;

    ed = usbd_interface2endpoint_descriptor(uaa->iface, 0);
    if (ed == NULL) {
        dprintf(("", "Could not read endpoint descriptor\n"));
        return NULL;
    }

    if (usbd_set_protocol(uaa->iface, 0)) {
        dprintf(("", "Set protocol failed\n"));
        return NULL;
    }

    softc->sc_ep_addr = ed->bEndpointAddress;


    if (TAILQ_EMPTY(&allukbds))
        _swix (OS_Claim, _INR(0,2), KEYV, keyv_entry, private_word);

    TAILQ_INSERT_TAIL (&allukbds, softc, link_kb);

    _swix (OS_CallAVector, _INR(0, 2) | _IN(9),
        KeyV_KeyboardPresent, KeyboardID_PC , MagicNoDebounce, KEYV);
    dprintf (("", "USB keyboard enabled\n"));

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

    /* Set up interrupt pipe. */
    usbd_open_pipe_intr(softc->sc_iface, softc->sc_ep_addr,
        USBD_SHORT_XFER_OK, &softc->sc_intrpipe, softc,
        &softc->data, sizeof(softc->data), ukbd_intr,
        USBD_DEFAULT_INTERVAL);

    return (struct device*) softc;
}

int detach_keyboard (struct device* kb)
{
    struct ukbd_softc* sc = (struct ukbd_softc*) kb;
    if(!sc || !sc->sc_intrpipe)
    {
      dprintf (("", "attempt to detach a NULL keyboard 'sc'\n"));
      return 0;
    }

    uint32_t * status = sc->status;
    /* release any keys held down */
    for (int w = 0; w < sizeof sc->status / sizeof (int); ++w)
    {
        if (status[w] == 0) continue;
        int key = w * 32;
        for (uint32_t ww = status[w]; ww; ww >>= 1, key++)
        {
            if ((ww & 1) == 0) continue;

            _swix (OS_CallAVector,

                _INR(0,1) | _IN(9),

                KeyV_KeyUp,
                key,
                KEYV
            );
        }
    }

    usbd_abort_pipe(sc->sc_intrpipe);
    usbd_close_pipe(sc->sc_intrpipe);
    TAILQ_REMOVE (&allukbds, sc, link_kb);
    if (TAILQ_EMPTY(&allukbds))
        _swix (OS_Release, _INR(0,2), KEYV, keyv_entry, private_word);
    free (kb);
    return 0;
}

int keyv (_kernel_swi_regs* r, void* pw)
{
    (void) pw;

    struct ukbd_softc* sc;
    TAILQ_FOREACH(sc, &allukbds, link_kb)
    {
        uint8_t * res = &sc->res;

        switch (r->r[0]) {
        case KeyV_EnableDrivers:
            memset (&sc->status[0], 0, sizeof sc->status);
            break;
        case KeyV_NotifyLEDState:
            *res = 0;
            if (r->r[1] & KeyV_LED_ScrollLock) *res |= SCROLL_LOCK;
            if (r->r[1] & KeyV_LED_NumLock) *res |= NUM_LOCK;
            if (r->r[1] & KeyV_LED_CapsLock) *res |= CAPS_LOCK;
            usbd_set_report_async(sc->sc_iface, UHID_OUTPUT_REPORT,
                0, res, 1);
            break;
        }
    }

    return 1;
}

#ifdef PS2KLUDGE
_kernel_oserror* keyup (_kernel_swi_regs* r, void* pw, void* k)
{
    (void) r;
    (void) pw;

//    dprintf (("", "Key Up: %x\n", (int) k));
    return _swix (OS_CallAVector,

        _INR(0,1) | _IN(9),

        KeyV_KeyUp,
        k,
        KEYV
    );
}
#endif

void ukbd_intr
(
    usbd_xfer_handle    xfer,
    usbd_private_handle addr,
    usbd_status         ustatus
)
{
    struct ukbd_softc *sc = addr;
    struct ukbd_data * data = &sc->data;
    struct ukbd_data * odata = &sc->odata;
    uint32_t * status = sc->status;
    int i;
    int key;
    int bit;
    uint8_t mods;
    uint8_t omods = odata->modifiers;
    uint8_t moddiff;
    uint32_t newstatus[8];

//    if (ustatus == USBD_CANCELLED)
//        return;

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

    mods = data->modifiers;

    /* check for error condition */
    if (data->keycode[0] == 1) return;

//    dprintf (("", "%02x %02x %02x %02x %02x %02x %02x %02x\n",
//        data->modifiers,
//        data->reserved,
//        data->keycode[0],
//        data->keycode[1],
//        data->keycode[2],
//        data->keycode[3],
//        data->keycode[4],
//        data->keycode[5]));

    memset (&newstatus[0], 0, sizeof newstatus);

    /* check each bit of the modifier field, if it's changed state,
       report the new state */
    moddiff = mods ^ omods;
    for (i = 0; i < 8; ++i) {
        if (moddiff & (1 << i)) {
            _swix (OS_CallAVector,

                _INR(0,1) | _IN(9),

                (mods & (1 << i))? KeyV_KeyDown: KeyV_KeyUp,
                mapping_table[0xe0 + i],
                KEYV
            );
        }
    }

    /* Scan new keys for key down event.  We have to construct the
    newstatus before we can check for key down. */
    for (i = 0; i < NKEYCODE; ++i) {
        key = mapping_table[data->keycode[i]];
        if (key != RSVD && key != NEQV) {
            bit = 1 << (key % 32);
            newstatus[key / 32] |= bit;
            if ((status[key / 32] & bit) == 0)
            {
                _swix (OS_CallAVector,

                    _INR(0,1) | _IN(9),

                    KeyV_KeyDown,
                    key,
                    KEYV
                );
//                dprintf (("", "Key Down: %x\n", key));
            }
        }
    }

    /* Scan old keys for key up event */
    for (i = 0; i < NKEYCODE; ++i) {
        key = mapping_table[odata->keycode[i]];
        if (key != RSVD && key != NEQV) {
            bit = 1 << (key % 32);
            if ((newstatus[key / 32] & bit) == 0)
            {
#ifdef PS2KLUDGE
                callx_add_callafter (2, keyup, (void*) key);
#else
                _swix (OS_CallAVector,

                    _INR(0,1) | _IN(9),

                    KeyV_KeyUp,
                    key,
                    KEYV
                );
//                dprintf (("", "Key Up: %x\n", key));
#endif
            }
        }
    }

    memcpy (sc->status, newstatus, sizeof sc->status);
    sc->odata = *data;
}