/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Implementation of Bus Mouse devices.
 *
 *          These devices were made by both Microsoft and Logitech. At
 *          first, Microsoft used the same protocol as Logitech, but did
 *          switch to their new protocol for their InPort interface. So,
 *          although alike enough to be handled in the same driver, they
 *          are not the same.
 *
 * NOTES:   Ported from Bochs with extensive modifications per testing
 *          of the real hardware, testing of drivers, and the old code.
 *
 *          Logitech Bus Mouse verified with:
 *            Linux Slackware 3.0
 *            Logitech LMouse.com 3.12
 *            Logitech LMouse.com 3.30
 *            Logitech LMouse.com 3.41
 *            Logitech LMouse.com 3.42
 *            Logitech LMouse.com 4.00
 *            Logitech LMouse.com 5.00
 *            Logitech LMouse.com 6.00
 *            Logitech LMouse.com 6.02 Beta
 *            Logitech LMouse.com 6.02
 *            Logitech LMouse.com 6.12
 *            Logitech LMouse.com 6.20
 *            Logitech LMouse.com 6.23
 *            Logitech LMouse.com 6.30
 *            Logitech LMouse.com 6.31E
 *            Logitech LMouse.com 6.34
 *            Logitech Mouse.exe 6.40
 *            Logitech Mouse.exe 6.41
 *            Logitech Mouse.exe 6.44
 *            Logitech Mouse.exe 6.46
 *            Logitech Mouse.exe 6.50
 *            Microsoft Mouse.com 2.00
 *            Microsoft Mouse.sys 3.00
 *            Microsoft Mouse.com 7.04
 *            Microsoft Mouse.com 8.21J
 *            Microsoft Windows 1.00 DR5
 *            Microsoft Windows 3.10.026
 *            Microsoft Windows 3.10.068 both MOUSE.DRV and LMOUSE.DRV
 *            Microsoft Windows NT 3.1
 *            Microsoft Windows 95
 *
 *          InPort verified with:
 *            Linux Slackware 3.0
 *            Logitech LMouse.com 6.12
 *            Logitech LMouse.com 6.41
 *            Microsoft Windows 3.10.068 both MOUSE.DRV and LMOUSE.DRV
 *            Microsoft Windows NT 3.1
 *            Microsoft Windows 98 SE
 *
 * Authors: Miran Grca, <mgrca8@gmail.com>
 *          Fred N. van Kempen, <decwiz@yahoo.com>
 *
 *          Copyright 200?-2019 Bochs.
 *          Copyright 2017-2019 Miran Grca.
 *          Copyright 1989-2019 Fred N. van Kempen.
 */
#include <inttypes.h>
#include <stdarg.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/io.h>
#include <86box/pic.h>
#include <86box/timer.h>
#include <86box/device.h>
#include <86box/mouse.h>
#include <86box/plat.h>
#include <86box/plat_unused.h>
#include <86box/random.h>

#define IRQ_MASK ((1 << 5) >> dev->irq)

/* MS Inport Bus Mouse Adapter */
#define INP_PORT_CONTROL      0x0000
#define INP_PORT_DATA         0x0001
#define INP_PORT_SIGNATURE    0x0002
#define INP_PORT_CONFIG       0x0003

#define INP_CTRL_READ_BUTTONS 0x00
#define INP_CTRL_READ_X       0x01
#define INP_CTRL_READ_Y       0x02
#define INP_CTRL_COMMAND      0x07
#define INP_CTRL_RAISE_IRQ    0x16
#define INP_CTRL_RESET        0x80

#define INP_HOLD_COUNTER      (1 << 5)
#define INP_ENABLE_TIMER_IRQ  (1 << 4)
#define INP_ENABLE_DATA_IRQ   (1 << 3)
#define INP_PERIOD_MASK       0x07

/* MS/Logictech Standard Bus Mouse Adapter */
#define BUSM_PORT_DATA      0x0000
#define BUSM_PORT_SIGNATURE 0x0001
#define BUSM_PORT_CONTROL   0x0002
#define BUSM_PORT_CONFIG    0x0003

#define HOLD_COUNTER        (1 << 7)
#define READ_X              (0 << 6)
#define READ_Y              (1 << 6)
#define READ_LOW            (0 << 5)
#define READ_HIGH           (1 << 5)
#define DISABLE_IRQ         (1 << 4)

#define DEVICE_ACTIVE       (1 << 7)

#define READ_X_LOW          (READ_X | READ_LOW)
#define READ_X_HIGH         (READ_X | READ_HIGH)
#define READ_Y_LOW          (READ_Y | READ_LOW)
#define READ_Y_HIGH         (READ_Y | READ_HIGH)

#define FLAG_INPORT         (1 << 0)
#define FLAG_ENABLED        (1 << 1)
#define FLAG_HOLD           (1 << 2)
#define FLAG_TIMER_INT      (1 << 3)
#define FLAG_DATA_INT       (1 << 4)

static const uint8_t periods[4] = { 30, 50, 100, 200 };

/* Our mouse device. */
typedef struct mouse {
    uint8_t current_b;
    uint8_t control_val;
    uint8_t config_val;
    uint8_t sig_val;
    uint8_t command_val;
    uint8_t pad;

    int8_t current_x;
    int8_t current_y;

    int base;
    int irq;
    int bn;
    int flags;
    int mouse_buttons;
    int mouse_buttons_last;
    int toggle_counter;
    int timer_enabled;

    double     period;
    pc_timer_t timer; /* mouse event timer */
} mouse_t;

#ifdef ENABLE_MOUSE_BUS_LOG
int bm_do_log = ENABLE_MOUSE_BUS_LOG;

static void
bm_log(const char *fmt, ...)
{
    va_list ap;

    if (bm_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define bm_log(fmt, ...)
#endif

/* Handle a READ operation from one of our registers. */
static uint8_t
lt_read(uint16_t port, void *priv)
{
    mouse_t *dev   = (mouse_t *) priv;
    uint8_t  value = 0xff;

    switch (port & 0x03) {
        case BUSM_PORT_DATA:
            /* Testing and another source confirm that the buttons are
             *ALWAYS* present, so I'm going to change this a bit. */
            switch (dev->control_val & 0x60) {
                case READ_X_LOW:
                    value = dev->current_x & 0x0F;
                    dev->current_x &= ~0x0F;
                    break;
                case READ_X_HIGH:
                    value = (dev->current_x >> 4) & 0x0F;
                    dev->current_x &= ~0xF0;
                    break;
                case READ_Y_LOW:
                    value = dev->current_y & 0x0F;
                    dev->current_y &= ~0x0F;
                    break;
                case READ_Y_HIGH:
                    value = (dev->current_y >> 4) & 0x0F;
                    dev->current_y &= ~0xF0;
                    break;
                default:
                    bm_log("ERROR: Reading data port in unsupported mode 0x%02x\n", dev->control_val);
            }
            value |= ((dev->current_b ^ 7) << 5);
            break;
        case BUSM_PORT_SIGNATURE:
            value = dev->sig_val;
            break;
        case BUSM_PORT_CONTROL:
            value = dev->control_val;
            dev->control_val |= 0x0F;
            /* If the conditions are right, simulate the flakiness of the correct IRQ bit. */
            if (dev->flags & FLAG_TIMER_INT)
                dev->control_val = (dev->control_val & ~IRQ_MASK) | (random_generate() & IRQ_MASK);
            break;
        case BUSM_PORT_CONFIG:
            /* Read from config port returns control_val in the upper 4 bits when enabled,
               possibly solid interrupt readout in the lower 4 bits, 0xff when not (at power-up). */
            if (dev->flags & FLAG_ENABLED)
                return (dev->control_val | 0x0F) & ~IRQ_MASK;
            else
                return 0xff;

        default:
            break;
    }

    bm_log("DEBUG: read from address 0x%04x, value = 0x%02x\n", port, value);

    return value;
}

static uint8_t
ms_read(uint16_t port, void *priv)
{
    mouse_t *dev   = (mouse_t *) priv;
    uint8_t  value = 0xff;

    switch (port & 0x03) {
        case INP_PORT_CONTROL:
            value = dev->control_val;
            break;
        case INP_PORT_DATA:
            switch (dev->command_val) {
                case INP_CTRL_READ_BUTTONS:
                    value = dev->current_b;
                    break;
                case INP_CTRL_READ_X:
                    value          = dev->current_x;
                    dev->current_x = 0;
                    break;
                case INP_CTRL_READ_Y:
                    value          = dev->current_y;
                    dev->current_y = 0;
                    break;
                case INP_CTRL_COMMAND:
                    value = dev->control_val;
                    break;

                default:
                    bm_log("ERROR: Reading data port in unsupported mode 0x%02x\n", dev->control_val);
            }
            break;
        case INP_PORT_SIGNATURE:
            if (dev->toggle_counter)
                value = 0x12;
            else
                value = 0xDE;
            dev->toggle_counter ^= 1;
            break;
        case INP_PORT_CONFIG:
            bm_log("ERROR: Unsupported read from port 0x%04x\n", port);
            break;

        default:
            break;
    }

    bm_log("DEBUG: read from address 0x%04x, value = 0x%02x\n", port, value);

    return value;
}

/* Handle a WRITE operation to one of our registers. */
static void
lt_write(uint16_t port, uint8_t val, void *priv)
{
    mouse_t *dev = (mouse_t *) priv;
    uint8_t  bit;

    bm_log("DEBUG: write  to address 0x%04x, value = 0x%02x\n", port, val);

    switch (port & 0x03) {
        case BUSM_PORT_DATA:
            bm_log("ERROR: Unsupported write to port 0x%04x (value = 0x%02x)\n", port, val);
            break;
        case BUSM_PORT_SIGNATURE:
            dev->sig_val = val;
            break;
        case BUSM_PORT_CONTROL:
            dev->control_val = val | 0x0F;

            if (!(val & DISABLE_IRQ))
                dev->flags |= FLAG_TIMER_INT;
            else
                dev->flags &= ~FLAG_TIMER_INT;

            if (val & HOLD_COUNTER)
                dev->flags |= FLAG_HOLD;
            else
                dev->flags &= ~FLAG_HOLD;

            if (dev->irq != -1)
                picintc(1 << dev->irq);

            break;
        case BUSM_PORT_CONFIG:
            /*
             * The original Logitech design was based on using a
             * 8255 parallel I/O chip. This chip has to be set up
             * for proper operation, and this configuration data
             * is what is programmed into this register.
             *
             * A snippet of code found in the FreeBSD kernel source
             * explains the value:
             *
             * D7    =  Mode set flag (1 = active)
             * This indicates the mode of operation of D7:
             * 1 = Mode set, 0 = Bit set/reset
             * D6,D5 =  Mode selection (port A)
             *      00 = Mode 0 = Basic I/O
             *      01 = Mode 1 = Strobed I/O
             *      10 = Mode 2 = Bi-dir bus
             * D4    =  Port A direction (1 = input)
             * D3    =  Port C (upper 4 bits) direction. (1 = input)
             * D2    =  Mode selection (port B & C)
             *      0 = Mode 0 = Basic I/O
             *      1 = Mode 1 = Strobed I/O
             * D1    =  Port B direction (1 = input)
             * D0    =  Port C (lower 4 bits) direction. (1 = input)
             *
             * So 91 means Basic I/O on all 3 ports, Port A is an input
             * port, B is an output port, C is split with upper 4 bits
             * being an output port and lower 4 bits an input port, and
             * enable the sucker.  Courtesy Intel 8255 databook. Lars
             *
             * 1001 1011    9B  1111    Default state
             * 1001 0001    91  1001    Driver-initialized state
             * The only difference is - port C upper and port B go from
             * input to output.
             */
            if (val & DEVICE_ACTIVE) {
                /* Mode set/reset - enable this */
                dev->config_val = val;
                if (dev->timer_enabled)
                    dev->flags |= (FLAG_ENABLED | FLAG_TIMER_INT);
                else
                    dev->flags |= FLAG_ENABLED;
                dev->control_val = 0x0F & ~IRQ_MASK;
            } else {
                /* Single bit set/reset */
                bit = 1 << ((val >> 1) & 0x07); /* Bits 3-1 specify the target bit */
                if (val & 1)
                    dev->control_val |= bit; /* Set */
                else
                    dev->control_val &= ~bit; /* Reset */
            }
            break;

        default:
            break;
    }
}

/* Handle a WRITE operation to one of our registers. */
static void
ms_write(uint16_t port, uint8_t val, void *priv)
{
    mouse_t *dev = (mouse_t *) priv;

    bm_log("DEBUG: write  to address 0x%04x, value = 0x%02x\n", port, val);

    switch (port & 0x03) {
        case INP_PORT_CONTROL:
            /* Bit 7 is reset. */
            if (val & INP_CTRL_RESET)
                dev->control_val = 0;

            /* Bits 0-2 are the internal register index. */
            switch (val & 0x07) {
                case INP_CTRL_COMMAND:
                case INP_CTRL_READ_BUTTONS:
                case INP_CTRL_READ_X:
                case INP_CTRL_READ_Y:
                    dev->command_val = val & 0x07;
                    break;

                default:
                    bm_log("ERROR: Unsupported command written to port 0x%04x (value = 0x%02x)\n", port, val);
            }
            break;
        case INP_PORT_DATA:
            if (dev->irq != -1)
                picintc(1 << dev->irq);
            switch (dev->command_val) {
                case INP_CTRL_COMMAND:
                    if (val & INP_HOLD_COUNTER)
                        dev->flags |= FLAG_HOLD;
                    else
                        dev->flags &= ~FLAG_HOLD;

                    if (val & INP_ENABLE_TIMER_IRQ)
                        dev->flags |= FLAG_TIMER_INT;
                    else
                        dev->flags &= ~FLAG_TIMER_INT;

                    if (val & INP_ENABLE_DATA_IRQ)
                        dev->flags |= FLAG_DATA_INT;
                    else
                        dev->flags &= ~FLAG_DATA_INT;

                    switch (val & INP_PERIOD_MASK) {
                        case 0:
                            dev->period = 0.0;
                            timer_disable(&dev->timer);
                            dev->timer_enabled = 0;
                            break;

                        case 1:
                        case 2:
                        case 3:
                        case 4:
                            dev->period        = (1000000.0 / (double) periods[(val & INP_PERIOD_MASK) - 1]);
                            dev->timer_enabled = (val & INP_ENABLE_TIMER_IRQ) ? 1 : 0;
                            timer_disable(&dev->timer);
                            if (dev->timer_enabled)
                                timer_set_delay_u64(&dev->timer, (uint64_t) (dev->period * (double) TIMER_USEC));
                            bm_log("DEBUG: Timer is now %sabled at period %i\n", (val & INP_ENABLE_TIMER_IRQ) ? "en" : "dis", (int32_t) dev->period);
                            break;

                        case 6:
                            if ((val & INP_ENABLE_TIMER_IRQ) && (dev->irq != -1))
                                picint(1 << dev->irq);
                            dev->control_val &= INP_PERIOD_MASK;
                            dev->control_val |= (val & ~INP_PERIOD_MASK);
                            return;

                        default:
                            bm_log("ERROR: Unsupported period written to port 0x%04x (value = 0x%02x)\n", port, val);
                    }

                    dev->control_val = val;

                    break;

                default:
                    bm_log("ERROR: Unsupported write to port 0x%04x (value = 0x%02x)\n", port, val);
            }
            break;
        case INP_PORT_SIGNATURE:
        case INP_PORT_CONFIG:
            bm_log("ERROR: Unsupported write to port 0x%04x (value = 0x%02x)\n", port, val);
            break;

        default:
            break;
    }
}

/* The emulator calls us with an update on the host mouse device. */
static int
bm_poll(void *priv)
{
    mouse_t *dev = (mouse_t *) priv;
    int delta_x;
    int delta_y;
    int xor;
    int b = mouse_get_buttons_ex();

    if (!mouse_capture && !video_fullscreen)
        return 1;

    if (!(dev->flags & FLAG_ENABLED))
        return 1; /* Mouse is disabled, do nothing. */

    if (!mouse_state_changed()) {
        dev->mouse_buttons_last = 0x00;
        return 1; /* State has not changed, do nothing. */
    }

    /* Converts button states from MRL to LMR. */
    dev->mouse_buttons = (uint8_t) (((b & 1) << 2) | ((b & 2) >> 1));
    if (dev->bn == 3)
        dev->mouse_buttons |= ((b & 4) >> 1);

    if ((dev->flags & FLAG_INPORT) && !dev->timer_enabled) {
        /* This is an InPort mouse in data interrupt mode,
           so update bits 6-3 here. */

        /* If the mouse has moved, set bit 6. */
        if (mouse_moved())
            dev->mouse_buttons |= 0x40;

        /* Set bits 3-5 according to button state changes. */
        xor = ((dev->current_b ^ dev->mouse_buttons) & 0x07) << 3;
        dev->mouse_buttons |= xor;
    }

    dev->mouse_buttons_last = b;

    if (!dev->timer_enabled) {
        /* If the counters are not frozen, update them. */
        if (!(dev->flags & FLAG_HOLD)) {
            mouse_subtract_coords(&delta_x, &delta_y, NULL, NULL, -128, 127, 0, 0);

            dev->current_x = (int8_t) delta_x;
            dev->current_y = (int8_t) delta_y;

            dev->current_b = dev->mouse_buttons;
        }

        /* Send interrupt. */
        if ((dev->flags & FLAG_DATA_INT) && (dev->irq != -1)) {
            picint(1 << dev->irq);
            bm_log("DEBUG: Data Interrupt Fired...\n");
        }
    }

    return 0;
}

/* The timer calls us on every tick if the mouse is in timer mode
   (InPort mouse is so configured, MS/Logitech Bus mouse always). */
static void
bm_update_data(mouse_t *dev)
{
    int delta_x;
    int delta_y;
    int xor;

    /* If the counters are not frozen, update them. */
    if ((mouse_capture || video_fullscreen) && !(dev->flags & FLAG_HOLD)) {
        /* Update the deltas and the delays. */
        mouse_subtract_coords(&delta_x, &delta_y, NULL, NULL, -128, 127, 0, 0);

        dev->current_x = (int8_t) delta_x;
        dev->current_y = (int8_t) delta_y;
    } else
        delta_x = delta_y = 0;

    if (dev->flags & FLAG_INPORT) {
        /* This is an InPort mouse in timer mode, so update current_b always,
           and update bits 6-3 (mouse moved and button state changed) here. */
        xor            = ((dev->current_b ^ dev->mouse_buttons) & 0x07) << 3;
        dev->current_b = (dev->mouse_buttons & 0x87) | xor;
        if (delta_x || delta_y)
            dev->current_b |= 0x40;
    } else if (!(dev->flags & FLAG_HOLD)) {
        /* This is a MS/Logitech Bus Mouse, so only update current_b if the
           counters are frozen. */
        dev->current_b = dev->mouse_buttons;
    }
}

/* Called at the configured period (InPort mouse) or 45 times per second (MS/Logitech Bus mouse). */
static void
bm_timer(void *priv)
{
    mouse_t *dev = (mouse_t *) priv;

    bm_log("DEBUG: Timer Tick (flags=%08X)...\n", dev->flags);

    /* The period is configured either via emulator settings (for MS/Logitech Bus mouse)
       or via software (for InPort mouse). */
    timer_advance_u64(&dev->timer, (uint64_t) (dev->period * (double) TIMER_USEC));

    if ((dev->flags & FLAG_TIMER_INT) && (dev->irq != -1)) {
        picint(1 << dev->irq);
        bm_log("DEBUG: Timer Interrupt Fired...\n");
    }

    bm_update_data(dev);
}

/* Release all resources held by the device. */
static void
bm_close(void *priv)
{
    mouse_t *dev = (mouse_t *) priv;

    if (dev)
        free(dev);
}

/* Set the mouse's IRQ. */
void
mouse_bus_set_irq(void *priv, int irq)
{
    mouse_t *dev = (mouse_t *) priv;

    dev->irq = irq;
}

/* Initialize the device for use by the user. */
static void *
bm_init(const device_t *info)
{
    mouse_t *dev;
    int      hz;

    dev = (mouse_t *) calloc(1, sizeof(mouse_t));

    if ((info->local & ~MOUSE_TYPE_ONBOARD) == MOUSE_TYPE_INPORT)
        dev->flags = FLAG_INPORT;
    else
        dev->flags = 0;

    if (info->local & MOUSE_TYPE_ONBOARD) {
        dev->base = 0x023c;
        dev->irq  = -1;
        dev->bn   = 2;
    } else {
        dev->base = device_get_config_hex16("base");
        dev->irq  = device_get_config_int("irq");
        dev->bn   = device_get_config_int("buttons");
    }
    mouse_set_buttons(dev->bn);

    dev->mouse_buttons      = 0;
    dev->mouse_buttons_last = 0;
    dev->sig_val            = 0; /* the signature port value */
    dev->current_x = dev->current_y = 0;
    dev->current_b                  = 0;
    dev->command_val                = 0; /* command byte */
    dev->toggle_counter             = 0; /* signature byte / IRQ bit toggle */
    dev->period                     = 0.0;

    timer_add(&dev->timer, bm_timer, dev, 0);

    if (dev->flags & FLAG_INPORT) {
        dev->control_val = 0; /* the control port value */
        dev->flags |= FLAG_ENABLED;

        io_sethandler(dev->base, 4,
                      ms_read, NULL, NULL, ms_write, NULL, NULL, dev);

        dev->timer_enabled = 0;
    } else {
        dev->control_val = 0x0f; /* the control port value */
        dev->config_val  = 0x9b; /* the config port value - 0x9b is the
                                    default state of the 8255: all ports
                                    are set to input */

        hz = device_get_config_int("hz");
        if (hz > 0)
            dev->period = (1000000.0 / (double) hz);

        io_sethandler(dev->base, 4,
                      lt_read, NULL, NULL, lt_write, NULL, NULL, dev);

        if (hz > 0) {
            timer_set_delay_u64(&dev->timer, (uint64_t) (dev->period * (double) TIMER_USEC));
            dev->timer_enabled = 1;
        } else {
            dev->flags |= FLAG_DATA_INT;
            dev->timer_enabled = 0;
        }
    }

    if (dev->flags & FLAG_INPORT)
        bm_log("MS Inport BusMouse initialized\n");
    else
        bm_log("Standard MS/Logitech BusMouse initialized\n");

    mouse_set_sample_rate(0.0);

    mouse_set_poll(bm_poll, dev);

    return dev;
}

static const device_config_t lt_config[] = {
  // clang-format off
    {
        .name           = "base",
        .description    = "Address",
        .type           = CONFIG_HEX16,
        .default_string = NULL,
        .default_int    = 0x23c,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "0x230", .value = 0x230 },
            { .description = "0x234", .value = 0x234 },
            { .description = "0x238", .value = 0x238 },
            { .description = "0x23C", .value = 0x23c },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "irq",
        .description    = "IRQ",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 5,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "IRQ 2", .value = 2 },
            { .description = "IRQ 3", .value = 3 },
            { .description = "IRQ 4", .value = 4 },
            { .description = "IRQ 5", .value = 5 },
            { .description = ""                  }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "hz",
        .description    = "Hz",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 45,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Non-timed (original)",       .value =  0 },
            { .description = "30 Hz (JMP2 = 1)",           .value = 30 },
            { .description = "45 Hz (JMP2 not populated)", .value = 45 },
            { .description = "60 Hz (JMP2 = 2)",           .value = 60 },
            { .description = ""                                        }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "buttons",
        .description    = "Buttons",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 2,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Two",   .value = 2 },
            { .description = "Three", .value = 3 },
            { .description = ""                  }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
  // clang-format on
};

static const device_config_t ms_config[] = {
  // clang-format off
    {
        .name           = "base",
        .description    = "Address",
        .type           = CONFIG_HEX16,
        .default_string = NULL,
        .default_int    = 0x23c,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "0x230", .value = 0x230 },
            { .description = "0x234", .value = 0x234 },
            { .description = "0x238", .value = 0x238 },
            { .description = "0x23C", .value = 0x23c },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "irq",
        .description    = "IRQ",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 5,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "IRQ 2", .value = 2 },
            { .description = "IRQ 3", .value = 3 },
            { .description = "IRQ 4", .value = 4 },
            { .description = "IRQ 5", .value = 5 },
            { .description = ""                  }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "buttons",
        .description    = "Buttons",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 2,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Two",   .value = 2 },
            { .description = "Three", .value = 3 },
            { .description = ""                  }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
  // clang-format on
};

const device_t mouse_logibus_device = {
    .name          = "Logitech/Microsoft Bus Mouse",
    .internal_name = "logibus",
    .flags         = DEVICE_ISA | DEVICE_SIDECAR,
    .local         = MOUSE_TYPE_LOGIBUS,
    .init          = bm_init,
    .close         = bm_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = lt_config
};

const device_t mouse_logibus_onboard_device = {
    .name          = "Logitech Bus Mouse (On-Board)",
    .internal_name = "logibus_onboard",
    .flags         = DEVICE_ISA,
    .local         = MOUSE_TYPE_LOGIBUS | MOUSE_TYPE_ONBOARD,
    .init          = bm_init,
    .close         = bm_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};

const device_t mouse_msinport_device = {
    .name          = "Microsoft Bus Mouse (InPort)",
    .internal_name = "msbus",
    .flags         = DEVICE_ISA | DEVICE_SIDECAR,
    .local         = MOUSE_TYPE_INPORT,
    .init          = bm_init,
    .close         = bm_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = ms_config
};
