#include "config.h"
#include "types.h"

#include "timer.h"
#include "memory.h"

#include "joy.h"
#include "sys.h"
#include "vdp.h"
#include "z80_ctrl.h"


#define JOY_TYPE_SHIFT          12


static vu8 joyType[8];
static vu16 joyState[8];
static vs16 joyAxisX[8];
static vs16 joyAxisY[8];

static u8 portSupport[2];
static u8 portType[2];

static u8 retry;
static u8 phase;

static u8 gun;
static u8 extSet;
static u16 gport;


static JoyEventCallback *joyEventCB;


void JOY_init()
{
    /* Reset controller state change event callback */
    joyEventCB = NULL;

    /* Then perform controller port reset and peripheral detection */
    JOY_reset();
}

void JOY_reset()
{
    vu8 *pb;
    u8 a, b;
    u8 id;
    u16 i;
#if (HALT_Z80_ON_IO == 1)
    u16 z80state;
#endif

    gport = 0xFFFF;

    /* disable ints (we can do it here for safety) */
    SYS_disableInts();

#if (HALT_Z80_ON_IO == 1)
    z80state = Z80_getAndRequestBus(TRUE);
#endif

    /* check for EA 4-Way Play */
    pb = (vu8 *)0xa10009;
    *pb = 0x40;
    pb = (vu8 *)0xa1000b;
    *pb = 0x7F;
    pb = (vu8 *)0xa10003;
    *pb = 0x40;

    pb = (vu8 *)0xa10005;
    *pb = 0x0C;
    asm volatile ("nop");
    asm volatile ("nop");
    pb = (vu8 *)0xa10003;
    a = *pb & 3;

    pb = (vu8 *)0xa10005;
    *pb = 0x7C;
    asm volatile ("nop");
    asm volatile ("nop");
    pb = (vu8 *)0xa10003;
    b = *pb & 3;

    /* EA 4-Way Play detected */
    if (a != 0 && b == 0)
    {
        /* EA 4-Way Play is the only thing that can be plugged in as it takes both ports */
        portType[PORT_1] = PORT_TYPE_EA4WAYPLAY;
        portType[PORT_2] = PORT_TYPE_EA4WAYPLAY;
        portSupport[PORT_1] = JOY_SUPPORT_EA4WAYPLAY;
        portSupport[PORT_2] = JOY_SUPPORT_OFF;

        for (i=JOY_1; i<JOY_NUM; i++)
        {
            joyType[i] = JOY_TYPE_UNKNOWN; /* default to unknown */
            joyState[i] = 0;
            joyAxisX[i] = 0;
            joyAxisY[i] = 0;
        }
    }
    else
    {
        /*
         * Initialize ports for peripheral interface protocol - default to
         * TH Control Method for pads
         */

        /* set the port bits direction */
        pb = (vu8 *)0xa10009;
        *pb = 0x40;
        pb += 2;
        *pb = 0x40;
        pb += 2;
        *pb = 0x40;
        /* set the port bits value */
        pb = (vu8 *)0xa10003;
        *pb = 0x40;
        pb += 2;
        *pb = 0x40;
        pb += 2;
        *pb = 0x40;

#if (HALT_Z80_ON_IO == 1)
        if (!z80state) Z80_releaseBus();
#endif

        /* need to wait */
        VDP_waitVSync();
        VDP_waitVSync();

#if (HALT_Z80_ON_IO == 1)
        z80state = Z80_getAndRequestBus(TRUE);
#endif

        /* get ID port 1 */
        pb = (vu8 *)0xa10003;
        a = *pb;
        *pb = 0x00;
        id = (a & 8) | (a & 4) ? 8 : 0;
        id |= (a & 2) | (a & 1) ? 4 : 0;
        a = *pb;
        *pb = 0x40;
        id |= (a & 8) | (a & 4) ? 2 : 0;
        id |= (a & 2) | (a & 1) ? 1 : 0;
        portType[PORT_1] = id;

        /* get ID port 2 */
        pb = (vu8 *)0xa10005;
        a = *pb;
        *pb = 0x00;
        id = (a & 8) | (a & 4) ? 8 : 0;
        id |= (a & 2) | (a & 1) ? 4 : 0;
        a = *pb;
        *pb = 0x40;
        id |= (a & 8) | (a & 4) ? 2 : 0;
        id |= (a & 2) | (a & 1) ? 1 : 0;
        portType[PORT_2] = id;

        /* now set the port support */
        portSupport[PORT_1] = JOY_SUPPORT_OFF; /* default to off */
        portSupport[PORT_2] = JOY_SUPPORT_OFF; /* default to off */

        for (i=JOY_1; i<JOY_NUM; i++)
        {
            joyType[i] = JOY_TYPE_UNKNOWN; /* default to unknown */
            joyState[i] = 0;
            joyAxisX[i] = 0;
            joyAxisY[i] = 0;
        }

        switch (portType[PORT_1])
        {
            case PORT_TYPE_MENACER:
            case PORT_TYPE_JUSTIFIER:
                /* init port for light gun control */
                pb = (vu8 *)0xa10009;
                *pb = 0x30;
                pb = (vu8 *)0xa10003;
                *pb = 0x30;
                break;
            case PORT_TYPE_MOUSE:
            case PORT_TYPE_TEAMPLAYER:
                /* init port for Three Line Handshake Method */
                pb = (vu8 *)0xa10009;
                *pb = 0x60;
                pb = (vu8 *)0xa10003;
                *pb = 0x60;
                break;
            case PORT_TYPE_PAD:
                portSupport[PORT_1] = JOY_SUPPORT_6BTN; /* default to on for pads */
                break;
        }

        switch (portType[PORT_2])
        {
            case PORT_TYPE_MENACER:
            case PORT_TYPE_JUSTIFIER:
                /* init port for light gun control */
                pb = (vu8 *)0xa1000b;
                *pb = 0x30;
                pb = (vu8 *)0xa10005;
                *pb = 0x30;
                break;
            case PORT_TYPE_MOUSE:
            case PORT_TYPE_TEAMPLAYER:
                /* init port for Three Line Handshake Method */
                pb = (vu8 *)0xa1000b;
                *pb = 0x60;
                pb = (vu8 *)0xa10005;
                *pb = 0x60;
                break;
            case PORT_TYPE_PAD:
                portSupport[PORT_2] = JOY_SUPPORT_6BTN; /* default to on for pads */
                break;
        }

        /* check if need to turn on an input device */
        if ((portType[PORT_1] != PORT_TYPE_PAD) && (portType[PORT_2] != PORT_TYPE_PAD))
        {
            /* no pads - look for teamplayer or mouse */
            if (portType[PORT_1] == PORT_TYPE_TEAMPLAYER)
                JOY_setSupport(PORT_1, JOY_SUPPORT_TEAMPLAYER);
            else if (portType[PORT_2] == PORT_TYPE_TEAMPLAYER)
                JOY_setSupport(PORT_2, JOY_SUPPORT_TEAMPLAYER);
            else if (portType[PORT_1] == PORT_TYPE_MOUSE)
                JOY_setSupport(PORT_1, JOY_SUPPORT_MOUSE);
            else if (portType[PORT_2] == PORT_TYPE_MOUSE)
                JOY_setSupport(PORT_2, JOY_SUPPORT_MOUSE);
        }
    }

#if (HALT_Z80_ON_IO == 1)
    if (!z80state) Z80_releaseBus();
#endif

    /* now update pads to reflect true type (3 or 6 button) */
    if (portType[PORT_1] == PORT_TYPE_PAD)
        if (joyType[JOY_1] == JOY_TYPE_PAD3)
            portSupport[PORT_1] = JOY_SUPPORT_3BTN;
    if (portType[PORT_2] == PORT_TYPE_PAD)
        if (joyType[JOY_2] == JOY_TYPE_PAD3)
            portSupport[PORT_2] = JOY_SUPPORT_3BTN;

    /* restore ints */
    SYS_enableInts();

    /* wait a few vblanks for JOY_update() to get valid data */
    SYS_doVBlankProcess();
    SYS_doVBlankProcess();
    SYS_doVBlankProcess();
}


void JOY_setEventHandler(JoyEventCallback *CB)
{
    joyEventCB = CB;
}


static void externalIntCB()
{
    vu8 *pb;
    u16 hv;
#if (HALT_Z80_ON_IO == 1)
    u16 z80state;
#endif

    hv = GET_HVCOUNTER;                     /* read HV counter */

    if (extSet || (gport == 0xFFFF)) return;

#if (HALT_Z80_ON_IO == 1)
    z80state = Z80_getAndRequestBus(TRUE);
#endif

    pb = (vu8 *)0xa10003 + gport*2;
    if (portType[gport] == PORT_TYPE_JUSTIFIER)
        *pb = gun | 0x10;                   /* deselect gun */

#if (HALT_Z80_ON_IO == 1)
    if (!z80state) Z80_releaseBus();
#endif

    if (!gun)
    {
        /* blue gun or menacer or phaser */
        joyAxisX[gport] = hv & 0x00FF;
        joyAxisY[gport] = hv >> 8;
    }
    else
    {
        /* red gun */
        joyAxisX[gport ? JOY_6 : JOY_3] = hv & 0x00FF;
        joyAxisY[gport ? JOY_6 : JOY_3] = hv >> 8;
    }
    extSet = 1;                             /* we got light sense */
}


void JOY_setSupport(u16 port, u16 support)
{
#if (HALT_Z80_ON_IO == 1)
    u16 z80state;
#endif

    if (port > PORT_2) return;

#if (HALT_Z80_ON_IO == 1)
    z80state = Z80_getAndRequestBus(TRUE);
#endif

    if ((portType[port] == PORT_TYPE_MENACER) || (portType[port] == PORT_TYPE_JUSTIFIER))
    {
        vu8 *pb;
        u8 val;

        if ((support == JOY_SUPPORT_JUSTIFIER_BLUE) || (support == JOY_SUPPORT_JUSTIFIER_BOTH) || (support == JOY_SUPPORT_MENACER))
        {
            /* enable lightgun support if support currently off */
            if (portSupport[port] == JOY_SUPPORT_OFF)
            {
                SYS_setInterruptMaskLevel(7);   /* disable ints */

                /* OBVIOUSLY blue gun or menacer is present due to portType ID */
                joyType[port] = (support == JOY_SUPPORT_MENACER) ? JOY_TYPE_MENACER : JOY_TYPE_JUSTIFIER;
                joyState[port] = 0;
                joyAxisX[port] = -1;
                joyAxisY[port] = -1;
                gun = 0;
                extSet = 0;
                gport = port;
                if (support == JOY_SUPPORT_JUSTIFIER_BOTH)
                {
                    joyType[port ? JOY_6 : JOY_3] = JOY_TYPE_JUSTIFIER; /* allow red gun */
                    joyState[port ? JOY_6 : JOY_3] = 0;
                    joyAxisX[port ? JOY_6 : JOY_3] = -1;
                    joyAxisY[port ? JOY_6 : JOY_3] = -1;
                }

                SYS_setExtIntCallback(externalIntCB);

                pb = (vu8 *)0xa10009 + port*2;
                *pb = 0xB0;                     /* enable TH->HL */
                VDP_setHVLatching(TRUE);           /* enable HV counter latch */
                val = VDP_getReg(11);
                VDP_setReg(11, val | 0x08);     /* set IE2, enable external int */

                SYS_setInterruptMaskLevel(1);   /* External int allowed */

                /* set last since this starts the vblank handling of the gun(s) */
                portSupport[port] = support;
            }
        }
        else if (support == JOY_SUPPORT_OFF)
        {
            /* disable lightgun support if was on */
            if ((portSupport[port] == JOY_SUPPORT_JUSTIFIER_BLUE) || (portSupport[port] == JOY_SUPPORT_JUSTIFIER_BOTH) || (portSupport[port] == JOY_SUPPORT_MENACER))
            {
                SYS_setInterruptMaskLevel(7);       /* disable ints */

                pb = (vu8 *)0xa10009 + port*2;
                *pb = 0x30;                         /* disable TH->HL */
                VDP_setHVLatching(FALSE);              /* disable HV counter latch */
                val = VDP_getReg(11);
                VDP_setReg(11, val & ~0x08);        /* clear IE2, disable external int */

                SYS_setExtIntCallback(NULL);

                if (port == PORT_1)
                {
                    joyType[JOY_1] = JOY_TYPE_UNKNOWN;
                    joyType[JOY_3] = JOY_TYPE_UNKNOWN;
                    joyState[JOY_1] = 0;
                    joyState[JOY_3] = 0;
                }
                else
                {
                    joyType[JOY_2] = JOY_TYPE_UNKNOWN;
                    joyType[JOY_6] = JOY_TYPE_UNKNOWN;
                    joyState[JOY_2] = 0;
                    joyState[JOY_6] = 0;
                }
                gport = 0xFFFF;

                SYS_setInterruptMaskLevel(3);       /* External int disallowed */
            }
            portSupport[port] = JOY_SUPPORT_OFF;
        }
        else if (support == JOY_SUPPORT_TRACKBALL)
        {
            /*
             * The port ID for the trackball is 0x0F the first time, then
             * 0x00 (same as menacer) until you power off.
             *
             * Reset port direction and data for TH Control Method.
             */
            pb = (vu8 *)0xa10009 + port*2;
            *pb = 0x40;
            pb = (vu8 *)0xa10003 + port*2;
            *pb = 0x40;
            portSupport[port] = support;
        }
    }
    else if ((support == JOY_SUPPORT_PHASER) || (support == JOY_SUPPORT_OFF))
    {
        vu8 *pb;
        u8 val;

        if (support == JOY_SUPPORT_PHASER)
        {
            /* enable lightgun support if support currently off */
            if (portSupport[port] == JOY_SUPPORT_OFF)
            {
                SYS_setInterruptMaskLevel(7);   /* disable ints */

                joyType[port] = JOY_TYPE_PHASER;
                joyState[port] = 0;
                joyAxisX[port] = -1;
                joyAxisY[port] = -1;
                gun = 0;
                extSet = 0;
                gport = port;

                SYS_setExtIntCallback(externalIntCB);

                pb = (vu8 *)0xa10009 + port*2;
                *pb = 0x80;                     /* enable TH->HL */
                VDP_setHVLatching(TRUE);           /* enable HV counter latch */
                val = VDP_getReg(11);
                VDP_setReg(11, val | 0x08);     /* set IE2, enable external int */

                SYS_setInterruptMaskLevel(1);   /* External int allowed */

                /* set last since this starts the vblank handling of the gun(s) */
                portSupport[port] = support;
            }
        }
        else if (support == JOY_SUPPORT_OFF)
        {
            /* disable lightgun support if was on */
            if (portSupport[port] == JOY_SUPPORT_PHASER)
            {
                SYS_setInterruptMaskLevel(7);       /* disable ints */

                pb = (vu8 *)0xa10009 + port*2;
                *pb = 0x40;                         /* disable TH->HL */
                VDP_setHVLatching(FALSE);              /* disable HV counter latch */
                val = VDP_getReg(11);
                VDP_setReg(11, val & ~0x08);        /* clear IE2, disable external int */

                SYS_setExtIntCallback(NULL);

                if (port == PORT_1)
                {
                    joyType[JOY_1] = JOY_TYPE_UNKNOWN;
                    joyState[JOY_1] = 0;
                }
                else
                {
                    joyType[JOY_2] = JOY_TYPE_UNKNOWN;
                    joyState[JOY_2] = 0;
                }
                gport = 0xFFFF;

                SYS_setInterruptMaskLevel(3);       /* External int disallowed */
            }
            portSupport[port] = JOY_SUPPORT_OFF;
        }
    }
    else
        portSupport[port] = support;

#if (HALT_Z80_ON_IO == 1)
    if (!z80state) Z80_releaseBus();
#endif
}


u8 JOY_getPortType(u16 port)
{
    if (port > PORT_2) return PORT_TYPE_UNKNOWN;

    return portType[port];
}

u8 JOY_getJoypadType(u16 joy)
{
    if (joy < JOY_NUM)
        return joyType[joy];

    return JOY_TYPE_UNKNOWN;
}

u16 JOY_readJoypad(u16 joy)
{
    if (joy == JOY_ALL)
    {
        u16 i;
        u16 res;

        res = 0;
        i = JOY_NUM;
        while(i--) res |= joyState[i] &  BUTTON_ALL;

        return res;
    }
    else if (joy < JOY_NUM)
        return joyState[joy];

    return 0;
}


s16 JOY_readJoypadX(u16 joy)
{
    if (joy < JOY_NUM)
        return joyAxisX[joy];

    return 0;
}

s16 JOY_writeJoypadX(u16 joy, u16 pos)
{
    if (joy < JOY_NUM)
        joyAxisX[joy]=pos;

    return 0;
}

s16 JOY_readJoypadY(u16 joy)
{
    if (joy < JOY_NUM)
        return joyAxisY[joy];

    return 0;
}

s16 JOY_writeJoypadY(u16 joy, u16 pos)
{
    if (joy < JOY_NUM)
        joyAxisY[joy]=pos;

    return 0;
}


void JOY_waitPressBtn()
{
    JOY_waitPressTime(JOY_ALL, BUTTON_BTN, 0);
}

u16 JOY_waitPressBtnTime(u16 time)
{
    return JOY_waitPressTime(JOY_ALL, BUTTON_BTN, time);
}

u16 JOY_waitPress(u16 joy, u16 btn)
{
    return JOY_waitPressTime(joy, btn, 0);
}

u16 JOY_waitPressTime(u16 joy, u16 btn, u16 time)
{
    u32 maxtime;
    u32 current;

    // (getTime() << 2) give a good estimation of time in ms
    current = (getTime(TRUE) << 2);

    // time == 0 --> wait indefinitely
    if (time) maxtime = current + time;
    else maxtime = 0xFFFFFFFF;

    while(current < maxtime)
    {
        u16 state;

        // more than 1 frame remaining ? directly use SYS_doVBlankProcess()
        if ((maxtime - current) >= 20) SYS_doVBlankProcess();
        // just update JOY states
        else JOY_update();

        if (joy == JOY_ALL)
        {
            u16 i;

            i = JOY_NUM;
            while(i--)
            {
                state = joyState[i] & btn;

                if (state)
                    return state;
            }
        }
        else
        {
            state = joyState[joy] & btn;

            if (state)
                return state;
        }

        // (getTime() << 2) give a good estimation of time in ms
        current = (getTime(TRUE) << 2);
    }

    return FALSE;
}

static u16 TH_CONTROL_PHASE(vu8 *pb)
{
    u16 val;

    *pb = 0x00; /* TH (select) low */
    asm volatile ("nop");
    asm volatile ("nop");
    val = *pb;

    *pb = 0x40; /* TH (select) high */
    val <<= 8;
    val |= *pb;

    return val;
}

static u16 read3Btn(u16 port)
{
    vu8 *pb;
    u16 val;

    pb = (vu8 *)0xa10003 + port*2;

    val = TH_CONTROL_PHASE(pb);                   /* - 0 s a 0 0 d u - 1 c b r l d u */
    val = ((val & 0x3000) >> 6) | (val & 0x003F); /* 0 0 0 0 0 0 0 0 s a c b r l d u */
    val ^= 0x00FF;                                /* 0 0 0 0 0 0 0 0 S A C B R L D U */

    return val | (JOY_TYPE_PAD3 << JOY_TYPE_SHIFT);
}

static u16 read6Btn(u16 port)
{
    vu8 *pb;
    u16 val, v1, v2;

    pb = (vu8 *)0xa10003 + port*2;

    v1 = TH_CONTROL_PHASE(pb);                    /* - 0 s a 0 0 d u - 1 c b r l d u */
    val = TH_CONTROL_PHASE(pb);                   /* - 0 s a 0 0 d u - 1 c b r l d u */
    v2 = TH_CONTROL_PHASE(pb);                    /* - 0 s a 0 0 0 0 - 1 c b m x y z */
    val = TH_CONTROL_PHASE(pb);                   /* - 0 s a 1 1 x x - 1 c b r l d u */
                                                  /* x should be read as 1 on a 6 button controller but in some case we read 0 so take care of that */

    // On a six-button controller, bits 0-3 of the high byte will always read 0.
    // This corresponds to up + down on a 3-button controller, on some controller or emulator that is a possible combination so we try
    // to compensante using last TH low read which should return 1111 on 6 buttons controller (not 100% though).
    if (((v2 & 0x0F00) != 0x0000) || ((val & 0x0C00) == 0x0000)) v2 = (JOY_TYPE_PAD3 << JOY_TYPE_SHIFT) | 0x0F00; /* three button pad */
    else v2 = (JOY_TYPE_PAD6 << JOY_TYPE_SHIFT) | ((v2 & 0x000F) << 8); /* six button pad */

    val = ((v1 & 0x3000) >> 6) | (v1 & 0x003F);   /* 0 0 0 0 0 0 0 0 s a c b r l d u  */
    val |= v2;                                    /* 0 0 0 1 m x y z s a c b r l d u  or  0 0 0 0 1 1 1 1 s a c b r l d u */
    val ^= 0x0FFF;                                /* 0 0 0 1 M X Y Z S A C B R L D U  or  0 0 0 0 0 0 0 0 S A C B R L D U */

    return val;
}

static u8 THREELINE_HANDSHAKE(vu8 *pb, u8 ph)
{
    u8 val = 0, hs;

    *pb = ph; /* next phase in protocol */

    /* wait on handshake */
    hs = (ph >> 1) & 0x10;
    while (retry)
    {
        val = *pb;
        if ((val & 0x10) == hs)
            break; /* timeout */
        retry--;
    }

    return val & 0x0F;
}

static s16 start3lhs(u16 port, u8 *hdr, u16 len)
{
    vu8 *pb;
    u16 i;

    retry = 255;
    phase = 0x20; /* selected */

    /* make sure port direction is correct */
    pb = (vu8 *)0xa10009 + port*2;
    *pb = 0x60;

    /* make sure port is deselected to start at first phase */
    pb = (vu8 *)0xa10003 + port*2;
    *pb = 0x60;
    asm volatile ("nop");
    asm volatile ("nop");

    /* quick check for at least initial mouse/tap value to cut the amount
     * of time spent if a mouse/tap isn't really plugged in, but the game
     * still activates the support
     */
    i = *pb & 0x0F;
    if ((i != 0) && (i != 3))
        return -1;

    hdr[0] = THREELINE_HANDSHAKE(pb, 0x60);
    if (retry)
    {
        for (i=1; i<len; i++)
        {
            hdr[i] = THREELINE_HANDSHAKE(pb, phase);
            phase ^= 0x20;
            if (!retry)
                break; /* timeout */
        }
    }
    if (!retry)
        *pb = 0x60; /* timeout - end request */

    return retry ? 0 : -1;
}


static u16 readMouse(u16 port)
{
    vu8 *pb;
    u16 val, i, mx, my;
    s16 sts;
    u8 hdr[4], md[6];

    pb = (vu8 *)0xa10003 + port*2;
    val = (JOY_TYPE_MOUSE << JOY_TYPE_SHIFT);

    sts = start3lhs(port, hdr, 4);
    // only need to check 2 first nibbles
    if ((sts == 0) &&
        (hdr[0] == 0x00) &&
        (hdr[1] == 0x0B))
//    if ((sts == 0) &&
//        (hdr[0] == 0x00) &&
//        (hdr[1] == 0x0B) &&
//        (hdr[2] == 0x0F) &&
//        (hdr[3] == 0x0F))
    {
        /* handle mouse */
        for (i=0; i<6; i++)
        {
            md[i] = THREELINE_HANDSHAKE(pb, phase);
            phase ^= 0x20;
            if (!retry)
                break; /* timeout */
        }
        if (i == 6)
        {
            if (md[0] & 0x04)
                mx = 256; /* x overflow */
            else
                mx = md[2]<<4 | md[3];
            if (md[0] & 0x01)
                mx |= mx ? 0xFF00 : 0xFFFF; /* x sign extend */
            if (md[0] & 0x08)
                my = 256; /* y overflow */
            else
                my = md[4]<<4 | md[5];
            if (md[0] & 0x02)
                my |= my ? 0xFF00 : 0xFFFF; /* y sign extend */
            joyAxisX[port] += (s16)mx;
            joyAxisY[port] -= (s16)my;

            if (md[1] & 8) val |= BUTTON_START;
            if (md[1] & 4) val |= BUTTON_MMB;
            if (md[1] & 2) val |= BUTTON_RMB;
            if (md[1] & 1) val |= BUTTON_LMB;

            if ((s16)mx < -2) val |= BUTTON_LEFT;
            else if ((s16)mx > 2) val |= BUTTON_RIGHT;
            if ((s16)my < -2) val |= BUTTON_DOWN;
            else if ((s16)my > 2) val |= BUTTON_UP;
        }
    }
    *pb = 0x60; /* end request */

    return val;
}

static void readTeamPlayer(u16 port)
{
    static const u16 xlt_all[2][4] = {
        { JOY_1, JOY_3, JOY_4, JOY_5 },
        { JOY_2, JOY_6, JOY_7, JOY_8 }
    };

    vu8 *pb;
    u16 change;
    u16 val = 0, v1, v2, v3, mx, my, typ = JOY_TYPE_UNKNOWN, i, j;
    s16 sts;
    u8 hdr[8], md[6];
    const u16 *xlt_port = xlt_all[port];

    pb = (vu8 *)0xa10003 + port*2;

    sts = start3lhs(port, hdr, 8);
    if ((sts == 0) &&
        (hdr[0] == 0x03) &&
        (hdr[1] == 0x0F) &&
        (hdr[2] == 0x00) &&
        (hdr[3] == 0x00))
    {
        /* handle Sega Tap */
        for (j=0; j<4; j++)
        {
            const u16 xlt = xlt_port[j];

            switch(hdr[4+j])
            {
                case JOY_TYPE_UNKNOWN:
                    val = 0;
                    typ = JOY_TYPE_UNKNOWN;
                    break;
                case JOY_TYPE_PAD3:
                    v1 = THREELINE_HANDSHAKE(pb, phase); /* r l d u */
                    phase ^= 0x20;
                    if (!retry)
                        break; /* timeout */
                    v2 = THREELINE_HANDSHAKE(pb, phase); /* s a c b */
                    phase ^= 0x20;
                    if (!retry)
                        break; /* timeout */
                    val = (v2 << 4) | v1;
                    val ^= 0x00FF; /* 0 0 0 0 0 0 0 0 S A C B R L D U */
                    typ = JOY_TYPE_PAD3;
                    break;
                case JOY_TYPE_PAD6:
                    v1 = THREELINE_HANDSHAKE(pb, phase); /* r l d u */
                    phase ^= 0x20;
                    if (!retry)
                        break; /* timeout */
                    v2 = THREELINE_HANDSHAKE(pb, phase); /* s a c b */
                    phase ^= 0x20;
                    if (!retry)
                        break; /* timeout */
                    v3 = THREELINE_HANDSHAKE(pb, phase); /* m x y z */
                    phase ^= 0x20;
                    if (!retry)
                        break; /* timeout */
                    val = (v3 << 8) | (v2 << 4) | v1;
                    val ^= 0x0FFF; /* 0 0 0 1 M X Y Z S A C B R L D U */
                    typ = JOY_TYPE_PAD6;
                    break;
                case JOY_TYPE_MOUSE:
                    for (i=0; i<6; i++)
                    {
                        md[i] = THREELINE_HANDSHAKE(pb, phase);
                        phase ^= 0x20;
                        if (!retry)
                            break; /* timeout */
                    }
                    if (!retry)
                        break;

                    val = 0;
                    if (md[0] & 0x04)
                        mx = 256; /* x overflow */
                    else
                        mx = (md[2] << 4) | md[3];
                    if (md[0] & 0x01)
                        mx |= 0xFF00; /* x sign extend */
                    if (md[0] & 0x08)
                        my = 256; /* y overflow */
                    else
                        my = (md[4] << 4) | md[5];
                    if (md[0] & 0x02)
                        my |= 0xFF00; /* y sign extend */
                    joyAxisX[xlt] += (s16)mx;
                    joyAxisY[xlt] += (s16)my;

                    if (md[1] & 8) val |= BUTTON_START;
                    if (md[1] & 4) val |= BUTTON_MMB;
                    if (md[1] & 2) val |= BUTTON_RMB;
                    if (md[1] & 1) val |= BUTTON_LMB;

                    if ((s16)mx < -2) val |= BUTTON_LEFT;
                    else if ((s16)mx > 2) val |= BUTTON_RIGHT;
                    if ((s16)my < -2) val |= BUTTON_DOWN;
                    else if ((s16)my > 2) val |= BUTTON_UP;

                    typ = JOY_TYPE_MOUSE;
                    break;
            }
            if (!retry)
                break; /* timeout */

            change = joyState[xlt] ^ val;
            joyType[xlt] = typ;
            joyState[xlt] = val;
            if ((joyEventCB) && (change)) joyEventCB(xlt, change, val);
        }
    }
    *pb = 0x60; /* end request */
}

static void readEa4WayPlay()
{
    static const u16 xlt[4] = { JOY_1, JOY_3, JOY_4, JOY_5 };

    vu8 *pb;
    u16 change;
    u16 val, v1, v2, typ, ind, i;

    for (i=0; i<4; i++)
    {
        pb = (vu8 *)0xa10005;
        *pb = (i<<4) | 0x0C;                          /* select port i */

        pb = (vu8 *)0xa10003;
        v1 = TH_CONTROL_PHASE(pb);                    /* - 0 s a 0 0 d u - 1 c b r l d u */
        if (v1 & 0x0C00)
            continue;                                 /* no pad */
        val = TH_CONTROL_PHASE(pb);                   /* - 0 s a 0 0 d u - 1 c b r l d u */
        v2 = TH_CONTROL_PHASE(pb);                    /* - 0 s a 0 0 0 0 - 1 c b m x y z */
        val = TH_CONTROL_PHASE(pb);                   /* - 0 s a 1 1 1 1 - 1 c b r l d u */

        pb = (vu8 *)0xa10005;
        *pb = 0x7C;                                   /* deselect port */

        if ((val & 0x0F00) != 0x0F00)
        {
            typ = JOY_TYPE_PAD3;                      /* three button pad */
            v2 = 0x0F00;
        }
        else
        {
            typ = JOY_TYPE_PAD6;                      /* six button pad */
            v2 = (v2 & 0x000F) << 8;
        }

        val = ((v1 & 0x3000) >> 6) | (v1 & 0x003F);   /* 0 0 0 0 0 0 0 0 s a c b r l d u  */
        val |= v2;                                    /* 0 0 0 1 m x y z s a c b r l d u  or  0 0 0 0 1 1 1 1 s a c b r l d u */
        val ^= 0x0FFF;                                /* 0 0 0 1 M X Y Z S A C B R L D U  or  0 0 0 0 0 0 0 0 S A C B R L D U */

        ind = xlt[i];

        change = joyState[ind] ^ val;
        joyType[ind] = typ;
        joyState[ind] = val;
        if ((joyEventCB) && (change)) joyEventCB(ind, change, val);
    }
}

static void readLightgun(u16 port)
{
    vu8 *pb;
    u16 newstate, change;
    u16 val = 0;
    u32 tmp;

    pb = (vu8 *)0xa10003 + port*2;
    if (portType[port] == PORT_TYPE_JUSTIFIER)
        *pb = gun | 0x10;                   /* deselect gun */

    if (!extSet)
    {
        /* no light sense => gun not pointed at screen */
        if (!gun)
        {
            /* blue gun or menacer or phaser*/
            joyAxisX[port] = -1;
            joyAxisY[port] = -1;
        }
        else
        {
            /* red gun */
            joyAxisX[port ? JOY_6 : JOY_3] = -1;
            joyAxisY[port ? JOY_6 : JOY_3] = -1;
        }
    }

    if (portType[port] == PORT_TYPE_MENACER)
    {
        asm volatile ("move.w #160,%0\n"
            "1:\n\t"
            "dbra %0,1b\n\t"
            : "=d" (tmp) : : "cc"
        );
        *pb = 0xFF;                         /* deassert RST */
        asm volatile ("moveq #20,%0\n"
            "1:\n\t"
            "dbra %0,1b\n\t"
            : "=d" (tmp) : : "cc"
        );
        *pb = 0xDF;                         /* assert RST long */
        asm volatile ("moveq #60,%0\n"
            "1:\n\t"
            "dbra %0,1b\n\t"
            : "=d" (tmp) : : "cc"
        );
        *pb = 0xFF;                         /* deassert RST */
        asm volatile ("nop");
        *pb = 0xDF;                         /* assert RST long */
        asm volatile ("move.w #1080,%0\n"
            "1:\n\t"
            "dbra %0,1b\n\t"
            : "=d" (tmp) : : "cc"
        );
        val = *pb;
        *pb = 0xFF;                         /* deassert RST */
        asm volatile ("nop");
        *pb = 0xCF;                         /* assert RST short - clear counter */

        newstate = 0;
        if (val & 8) newstate |= BUTTON_START;
        if (val & 4) newstate |= BUTTON_C;
        if (val & 2) newstate |= BUTTON_A;
        if (val & 1) newstate |= BUTTON_B;

        change = joyState[port] ^ newstate;
        joyType[port] = JOY_TYPE_MENACER;
        joyState[port] = newstate;
        if ((joyEventCB) && (change)) joyEventCB(port, change, newstate);
    }
    else if (portType[port] == PORT_TYPE_JUSTIFIER)
    {
        *pb = 0x00;                         /* select blue gun */
        val = *pb;
        val = *pb;                          /* read twice... probably a setup delay thing */
        *pb = 0x10;                         /* deselect blue gun */

        newstate = 0;
        if (~val & 2) newstate |= BUTTON_START;
        if (~val & 1) newstate |= BUTTON_A;

        change = joyState[port] ^ newstate;
        joyType[port] = JOY_TYPE_JUSTIFIER;
        joyState[port] = newstate;
        if ((joyEventCB) && (change)) joyEventCB(port, change, newstate);

        if (joyType[port ? JOY_6 : JOY_3] == JOY_TYPE_JUSTIFIER)
        {
            *pb = 0x20;                     /* select red gun */
            val = *pb;
            val = *pb;                      /* read twice... probably a setup delay thing */
            *pb = 0x30;                     /* deselect red gun */

            newstate = 0;
            if (~val & 2) newstate |= BUTTON_START;
            if (~val & 1) newstate |= BUTTON_A;

            change = joyState[port ? JOY_6 : JOY_3] ^ newstate;
            joyType[port ? JOY_6 : JOY_3] = JOY_TYPE_JUSTIFIER;
            joyState[port ? JOY_6 : JOY_3] = newstate;
            if ((joyEventCB) && (change)) joyEventCB(port ? JOY_6 : JOY_3, change, newstate);

            /* only need to toggle gun selector if have two guns */
            gun ^= 0x20;
        }

        *pb = gun | 0x10;                   /* deselect gun */
        *pb = gun;                          /* leave gun selected to get light sense input */
    }
    else
    {
        /* check Phaser trigger */
        val = *pb;                          /* - - - t - - - - */
        newstate = 0;
        if (~val & 0x10) newstate = BUTTON_A;

        change = joyState[port] ^ newstate;
        joyType[port] = JOY_TYPE_PHASER;
        joyState[port] = newstate;
        if ((joyEventCB) && (change)) joyEventCB(port, change, newstate);
    }

    extSet = 0;                             /* clear light sensed flag */
}

static u16 readTrackball(u16 port)
{
    vu8 *pb;
    u8 mx, my, v1, v2, v3, v4;
    u16 val;
    u32 tmp;

    pb = (vu8 *)0xa10003 + port*2;
    val = (JOY_TYPE_TRACKBALL << JOY_TYPE_SHIFT);

    *pb = 0x00; /* TH (select) low */
    /* ~ 84 us */
    asm volatile ("moveq #63,%0\n"
        "1:\n\t"
        "dbra %0,1b\n\t"
        : "=d" (tmp) : : "cc"
    );
    v1 = *pb;

    *pb = 0x40; /* TH (select) high */
    /* ~ 42 us */
    asm volatile ("moveq #31,%0\n"
        "1:\n\t"
        "dbra %0,1b\n\t"
        : "=d" (tmp) : : "cc"
    );
    v2 = *pb;

    *pb = 0x00; /* TH (select) low */
    /* ~ 42 us */
    asm volatile ("moveq #31,%0\n"
        "1:\n\t"
        "dbra %0,1b\n\t"
        : "=d" (tmp) : : "cc"
    );
    v3 = *pb;

    *pb = 0x40; /* TH (select) high */
    /* ~ 42 us */
    asm volatile ("moveq #31,%0\n"
        "1:\n\t"
        "dbra %0,1b\n\t"
        : "=d" (tmp) : : "cc"
    );
    v4 = *pb;

    mx = (v1 & 0x0F) << 4;
    mx |= v2 & 0x0F;
    my = (v3 & 0x0F) << 4;
    my |= v4 & 0x0F;

    joyAxisX[port] -= (s8)mx;
    joyAxisY[port] += (s8)my;

    if (~v4 & 32) val |= BUTTON_A;
    if (~v4 & 16) val |= BUTTON_START;
    if ((s8)mx < -2) val |= BUTTON_RIGHT;
    else if ((s8)mx > 2) val |= BUTTON_LEFT;
    if ((s8)my < -2) val |= BUTTON_DOWN;
    else if ((s8)my > 2) val |= BUTTON_UP;

    return val;
}


void JOY_update()
{
    u16 val;
    u16 newstate;
    u16 change;
#if (HALT_Z80_ON_IO == 1)
    u16 z80state;
#endif

    /* disable ints */
    SYS_disableInts();

#if (HALT_Z80_ON_IO == 1)
    z80state = Z80_getAndRequestBus(TRUE);
#endif

    switch (portSupport[PORT_1])
    {
        case JOY_SUPPORT_OFF:
            break;

        case JOY_SUPPORT_3BTN:
            val = read3Btn(PORT_1);
            newstate = val & BUTTON_ALL;
            change = joyState[JOY_1] ^ newstate;
            joyType[JOY_1] = val >> JOY_TYPE_SHIFT;
            joyState[JOY_1] = newstate;
            if ((joyEventCB) && (change)) joyEventCB(JOY_1, change, newstate);
            break;

        case JOY_SUPPORT_6BTN:
            val = read6Btn(PORT_1);
            newstate = val & BUTTON_ALL;
            change = joyState[JOY_1] ^ newstate;
            joyType[JOY_1] = val >> JOY_TYPE_SHIFT;
            joyState[JOY_1] = newstate;
            if ((joyEventCB) && (change)) joyEventCB(JOY_1, change, newstate);
            break;

        case JOY_SUPPORT_MOUSE:
            val = readMouse(PORT_1);
            newstate = val & BUTTON_ALL;
            change = joyState[JOY_1] ^ newstate;
            joyType[JOY_1] = val >> JOY_TYPE_SHIFT;
            joyState[JOY_1] = newstate;
            if ((joyEventCB) && (change)) joyEventCB(JOY_1, change, newstate);
            break;

        case JOY_SUPPORT_TRACKBALL:
            val = readTrackball(PORT_1);
            newstate = val & BUTTON_ALL;
            change = joyState[JOY_1] ^ newstate;
            joyType[JOY_1] = val >> JOY_TYPE_SHIFT;
            joyState[JOY_1] = newstate;
            if ((joyEventCB) && (change)) joyEventCB(JOY_1, change, newstate);
            break;

        case JOY_SUPPORT_TEAMPLAYER:
            readTeamPlayer(PORT_1);
            break;

        case JOY_SUPPORT_EA4WAYPLAY:
            readEa4WayPlay();
            break;

        case JOY_SUPPORT_MENACER:
        case JOY_SUPPORT_JUSTIFIER_BLUE:
        case JOY_SUPPORT_JUSTIFIER_BOTH:
        case JOY_SUPPORT_PHASER:
            readLightgun(PORT_1);
            break;

        default:
            break;
    }

    switch (portSupport[PORT_2])
    {
        case JOY_SUPPORT_OFF:
            break;

        case JOY_SUPPORT_3BTN:
            val = read3Btn(PORT_2);
            newstate = val & BUTTON_ALL;
            change = joyState[JOY_2] ^ newstate;
            joyType[JOY_2] = val >> JOY_TYPE_SHIFT;
            joyState[JOY_2] = newstate;
            if ((joyEventCB) && (change)) joyEventCB(JOY_2, change, newstate);
            break;

        case JOY_SUPPORT_6BTN:
            val = read6Btn(PORT_2);
            newstate = val & BUTTON_ALL;
            change = joyState[JOY_2] ^ newstate;
            joyType[JOY_2] = val >> JOY_TYPE_SHIFT;
            joyState[JOY_2] = newstate;
            if ((joyEventCB) && (change)) joyEventCB(JOY_2, change, newstate);
            break;

        case JOY_SUPPORT_MOUSE:
            val = readMouse(PORT_2);
            newstate = val & BUTTON_ALL;
            change = joyState[JOY_2] ^ newstate;
            joyType[JOY_2] = val >> JOY_TYPE_SHIFT;
            joyState[JOY_2] = newstate;
            if ((joyEventCB) && (change)) joyEventCB(JOY_2, change, newstate);
            break;

        case JOY_SUPPORT_TRACKBALL:
            val = readTrackball(PORT_2);
            newstate = val & BUTTON_ALL;
            change = joyState[JOY_2] ^ newstate;
            joyType[JOY_2] = val >> JOY_TYPE_SHIFT;
            joyState[JOY_2] = newstate;
            if ((joyEventCB) && (change)) joyEventCB(JOY_2, change, newstate);
            break;

        case JOY_SUPPORT_TEAMPLAYER:
            readTeamPlayer(PORT_2);
            break;

        case JOY_SUPPORT_MENACER:
        case JOY_SUPPORT_JUSTIFIER_BLUE:
        case JOY_SUPPORT_JUSTIFIER_BOTH:
        case JOY_SUPPORT_PHASER:
            readLightgun(PORT_2);
            break;

        default:
            break;
    }

#if (HALT_Z80_ON_IO == 1)
    if (!z80state) Z80_releaseBus();
#endif

    /* restore ints */
    SYS_enableInts();
}
