/*
    JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine
    Release Version 2.4

    A project from the Physics Dept, The University of Oxford

    Copyright (C) 2007-2010 The University of Oxford

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2 as published by
    the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
    Details (including contact information) can be found at: 

    jpc.sourceforge.net
    or the developer website
    sourceforge.net/projects/jpc/

    Conceived and Developed by:
    Rhys Newman, Ian Preston, Chris Dennis

    End of licence header
*/

package org.jpc.emulator.peripheral;

import java.io.*;
import java.util.logging.*;

import org.jpc.emulator.motherboard.*;
import org.jpc.emulator.memory.*;
import org.jpc.emulator.processor.Processor;
import org.jpc.emulator.*;
import org.jpc.j2se.*;

/**
 * 
 * @author Chris Dennis
 */
public class Keyboard extends AbstractHardwareComponent implements IODevice
{
    private static final Logger LOGGING = Logger.getLogger(Keyboard.class.getName());
    private static final boolean LOG_ACCESS = false;
    
    /* Keyboard Controller Commands */
    private static final byte KBD_CCMD_READ_MODE = (byte)0x20; /* Read mode bits */
    private static final byte KBD_CCMD_WRITE_MODE = (byte)0x60; /* Write mode bits */
    private static final byte KBD_CCMD_GET_VERSION = (byte)0xA1; /* Get controller version */
    private static final byte KBD_CCMD_MOUSE_DISABLE = (byte)0xA7; /* Disable mouse interface */
    private static final byte KBD_CCMD_MOUSE_ENABLE = (byte)0xA8; /* Enable mouse interface */
    private static final byte KBD_CCMD_TEST_MOUSE = (byte)0xA9; /* Mouse interface test */
    private static final byte KBD_CCMD_SELF_TEST = (byte)0xAA; /* Controller self test */
    private static final byte KBD_CCMD_KBD_TEST = (byte)0xAB; /* Keyboard interface test */
    private static final byte KBD_CCMD_KBD_DISABLE = (byte)0xAD; /* Keyboard interface disable */
    private static final byte KBD_CCMD_KBD_ENABLE = (byte)0xAE; /* Keyboard interface enable */
    private static final byte KBD_CCMD_READ_INPORT = (byte)0xC0; /* read input port */
    private static final byte KBD_CCMD_READ_OUTPORT = (byte)0xD0; /* read output port */
    private static final byte KBD_CCMD_WRITE_OUTPORT = (byte)0xD1; /* write output port */
    private static final byte KBD_CCMD_WRITE_OBUF = (byte)0xD2;
    private static final byte KBD_CCMD_WRITE_AUX_OBUF = (byte)0xD3; /* Write to output buffer as if initiated by the auxiliary device */
    private static final byte KBD_CCMD_WRITE_MOUSE = (byte)0xD4; /* Write the following byte to the mouse */
    private static final byte KBD_CCMD_DISABLE_A20 = (byte)0xDD; /* HP vectra only ? */
    private static final byte KBD_CCMD_ENABLE_A20 = (byte)0xDF; /* HP vectra only ? */
    private static final byte KBD_CCMD_RESET = (byte)0xFE;

    /* Keyboard Commands */
    private static final byte KBD_CMD_SET_LEDS = (byte)0xED;; /* Set keyboard leds */
    private static final byte KBD_CMD_ECHO = (byte)0xEE;
    private static final byte KBD_CMD_GET_ID = (byte)0xF2; /* get keyboard ID */
    private static final byte KBD_CMD_SET_RATE = (byte)0xF3; /* Set typematic rate */
    private static final byte KBD_CMD_ENABLE = (byte)0xF4; /* Enable scanning */
    private static final byte KBD_CMD_RESET_DISABLE = (byte)0xF5; /* reset and disable scanning */
    private static final byte KBD_CMD_RESET_ENABLE = (byte)0xF6; /* reset and enable scanning */
    private static final byte KBD_CMD_RESET = (byte)0xFF; /* Reset */

    /* Keyboard Replies */
    private static final byte KBD_REPLY_POR = (byte)0xAA; /* Power on reset */
    private static final byte KBD_REPLY_ACK = (byte)0xFA; /* Command ACK */
    private static final byte KBD_REPLY_RESEND = (byte)0xFE; /* Command NACK, send the cmd again */

    /* Status Register Bits */
    private static final byte KBD_STAT_OBF = (byte)0x01; /* Keyboard output buffer full */
    private static final byte KBD_STAT_IBF = (byte)0x02; /* Keyboard input buffer full */
    private static final byte KBD_STAT_SELFTEST = (byte)0x04; /* Self test successful */
    private static final byte KBD_STAT_CMD = (byte)0x08; /* Last write was a command write (0=data) */
    private static final byte KBD_STAT_UNLOCKED = (byte)0x10; /* Zero if keyboard locked */
    private static final byte KBD_STAT_MOUSE_OBF = (byte)0x20; /* Mouse output buffer full */
    private static final byte KBD_STAT_GTO = (byte)0x40; /* General receive/xmit timeout */
    private static final byte KBD_STAT_PERR = (byte)0x80; /* Parity error */

    /* Controller Mode Register Bits */
    private static final int KBD_MODE_KBD_INT = 0x01; /* Keyboard data generate IRQ1 */
    private static final int KBD_MODE_MOUSE_INT = 0x02; /* Mouse data generate IRQ12 */
    private static final int KBD_MODE_SYS  = 0x04; /* The system flag (?) */
    private static final int KBD_MODE_NO_KEYLOCK = 0x08; /* The keylock doesn't affect the keyboard if set */
    private static final int KBD_MODE_DISABLE_KBD = 0x10; /* Disable keyboard interface */
    private static final int KBD_MODE_DISABLE_MOUSE = 0x20; /* Disable mouse interface */
    private static final int KBD_MODE_KCC = 0x40; /* Scan code conversion to PC format */
    private static final int KBD_MODE_RFU = 0x80;

    /* Mouse Commands */
    private static final byte AUX_SET_SCALE11 = (byte)0xE6; /* Set 1:1 scaling */
    private static final byte AUX_SET_SCALE21 = (byte)0xE7; /* Set 2:1 scaling */
    private static final byte AUX_SET_RES = (byte)0xE8; /* Set resolution */
    private static final byte AUX_GET_SCALE = (byte)0xE9; /* Get scaling factor */
    private static final byte AUX_SET_STREAM = (byte)0xEA; /* Set stream mode */
    private static final byte AUX_POLL = (byte)0xEB; /* Poll */
    private static final byte AUX_RESET_WRAP = (byte)0xEC; /* Reset wrap mode */
    private static final byte AUX_SET_WRAP = (byte)0xEE; /* Set wrap mode */
    private static final byte AUX_SET_REMOTE = (byte)0xF0; /* Set remote mode */
    private static final byte AUX_GET_TYPE = (byte)0xF2; /* Get type */
    private static final byte AUX_SET_SAMPLE = (byte)0xF3; /* Set sample rate */
    private static final byte AUX_ENABLE_DEV = (byte)0xF4; /* Enable aux device */
    private static final byte AUX_DISABLE_DEV = (byte)0xF5; /* Disable aux device */
    private static final byte AUX_SET_DEFAULT = (byte)0xF6;
    private static final byte AUX_RESET = (byte)0xFF; /* Reset aux device */
    private static final byte AUX_ACK = (byte)0xFA; /* Command byte ACK. */

    private static final byte MOUSE_STATUS_REMOTE = (byte)0x40;
    private static final byte MOUSE_STATUS_ENABLED = (byte)0x20;
    private static final byte MOUSE_STATUS_SCALE21 = (byte)0x10;

    private static final int MOUSE_TYPE = 0; /* 0 = PS2, 3 = IMPS/2, 4 = IMEX */

    private static final int KBD_QUEUE_SIZE = 256;

    //Instance Variables
    private final KeyboardQueue queue;

    private byte commandWrite;
    private byte status;
    private int mode;
    /* keyboard state */
    private int keyboardWriteCommand;
    private boolean keyboardScanEnabled;
    /* mouse state */ //Split this off?
    private int mouseWriteCommand;
    private int mouseStatus;
    private int mouseResolution;
    private int mouseSampleRate;
    private boolean mouseWrap;
    private int mouseDetectState;
    private int mouseDx;
    private int mouseDy;
    private int mouseDz;
    private int mouseButtons;

    private boolean ioportRegistered;

    private InterruptController irqDevice;
    private Processor cpu;
    private PhysicalAddressSpace physicalAddressSpace;
    private LinearAddressSpace linearAddressSpace;

    public Keyboard()
    {
        ioportRegistered = false;
        queue = new KeyboardQueue();
        physicalAddressSpace = null;
        linearAddressSpace = null;
        cpu = null;
        reset();
    }

    public void saveState(DataOutput output) throws IOException
    {
        output.writeByte(commandWrite);
        output.writeByte(status);
        output.writeByte(mode);
        output.writeInt(keyboardWriteCommand);
        output.writeBoolean(keyboardScanEnabled);
        output.writeInt(mouseWriteCommand);
        output.writeInt(mouseStatus);
        output.writeInt(mouseResolution);
        output.writeInt(mouseSampleRate);
        output.writeBoolean(mouseWrap);
        output.writeInt(mouseDetectState);
        output.writeInt(mouseDx);
        output.writeInt(mouseDy);
        output.writeInt(mouseDz);
        output.writeInt(mouseButtons);
        //dump keyboard queue
        queue.saveState(output);
    }

    public void loadState(DataInput input) throws IOException
    {
        ioportRegistered = false;
        commandWrite = input.readByte();
        status = input.readByte();
        mode = input.readByte();
        keyboardWriteCommand = input.readInt();
        keyboardScanEnabled = input.readBoolean();
        mouseWriteCommand = input.readInt();
        mouseStatus = input.readInt();
        mouseResolution = input.readInt();
        mouseSampleRate = input.readInt();
        mouseWrap = input.readBoolean();
        mouseDetectState = input.readInt();
        mouseDx = input.readInt();
        mouseDy = input.readInt();
        mouseDz = input.readInt();
        mouseButtons = input.readInt();
        //load keyboard queue
        queue.loadState(input);
    }

    //IODevice Methods
    public int[] ioPortsRequested()
    {
        return new int[]{0x60, 0x64};
    }

    public int ioPortRead8(int address)
    {
        switch (address) {
            case 0x60:
                int res = readData();
                if (LOG_ACCESS)
                    System.out.printf("Keyboard read address=%02x, result=%02x status=%02x\n", address, res, status);
                return res;
            case 0x64:
//                if (LOG_ACCESS)
//                    System.out.printf("Keyboard read address=%02x, result=%02x\n", address, status);
                return 0xff & status;
            default:
                return 0xffffffff;
        }
    }
    public int ioPortRead16(int address)
    {
        return (0xff & ioPortRead8(address)) | (0xff00 & ioPortRead8(address + 1));
    }
    public int ioPortRead32(int address)
    {
        return 0xffffffff;
    }

    public void ioPortWrite8(int address, int data)
    {
        switch (address) {
            case 0x60:
                writeData((byte)data);
                if (LOG_ACCESS)
                    System.out.printf("Keyboard write address=%02x, data=%02x status=%02x\n", address, data, status);
                break;
            case 0x64:
                writeCommand((byte)data);
                if (LOG_ACCESS)
                    System.out.printf("Keyboard write address=%02x, data=%02x status=%02x\n", address, data, status);
                break;
            default:
        }
    }
    public void ioPortWrite16(int address, int data)
    {
        ioPortWrite8(address, data);
        ioPortWrite8(address + 1, data >> 8);
    }

    public void ioPortWrite32(int address, int data)
    {
        ioPortWrite16(address, data);
        ioPortWrite16(address + 2, data >> 16);
    }

    public void reset()
    {
        irqDevice = null;
        cpu = null;
        physicalAddressSpace = null;
        linearAddressSpace = null;
        ioportRegistered = false;
	

        keyboardWriteCommand = -1;
        mouseWriteCommand = -1;
        mode = KBD_MODE_KBD_INT | KBD_MODE_MOUSE_INT;
        status = (byte)(KBD_STAT_CMD | KBD_STAT_UNLOCKED);
        queue.reset();

        commandWrite = 0;
        keyboardWriteCommand = 0;
        keyboardScanEnabled = false;
        mouseWriteCommand = 0;
        mouseStatus = 0;
        mouseResolution = 0;
        mouseSampleRate = 0;
        mouseWrap = false;
        mouseDetectState = 0;
        mouseDx = 0;
        mouseDy = 0;
        mouseDz = 0;
        mouseButtons = 0;
    }

    private void setGateA20State(boolean value)
    {
        physicalAddressSpace.setGateA20State(value);
    }

    private byte readData()
    {
        byte val = queue.readData();
        updateIRQ();
        return val;
    }

    private void writeData(byte data)
    {
        if (LOG_ACCESS)
            System.out.printf("Write Data: commandWrite=%08x ", commandWrite);
        switch(commandWrite) {
            case 0:
                writeKeyboard(data);
                break;
            case KBD_CCMD_WRITE_MODE:
                mode = 0xff & data;
                updateIRQ();
                break;
            case KBD_CCMD_WRITE_OBUF:
                queue.writeData(data, (byte)0);
                break;
            case KBD_CCMD_WRITE_AUX_OBUF:
                queue.writeData(data, (byte)1);
                break;
            case KBD_CCMD_WRITE_OUTPORT:
                setGateA20State((data & 0x2) != 0);
                if (0x1 != (data & 0x1))
                    cpu.reset();
                break;
            case KBD_CCMD_WRITE_MOUSE:
                writeMouse(data);
                break;
            default:
                break;
        }
        commandWrite = (byte)0x00;
        status &= ~KBD_STAT_CMD;
    }

    private void writeCommand(byte data)
    {
        switch(data) {
            case KBD_CCMD_READ_MODE:
                queue.writeData((byte)mode, (byte)0);
                break;
            case KBD_CCMD_WRITE_MODE:
            case KBD_CCMD_WRITE_OBUF:
            case KBD_CCMD_WRITE_AUX_OBUF:
            case KBD_CCMD_WRITE_MOUSE:
            case KBD_CCMD_WRITE_OUTPORT:
                commandWrite = data;
                break;
            case KBD_CCMD_MOUSE_DISABLE:
                mode |= KBD_MODE_DISABLE_MOUSE;
                break;
            case KBD_CCMD_MOUSE_ENABLE:
                mode &= ~KBD_MODE_DISABLE_MOUSE;
                break;
            case KBD_CCMD_TEST_MOUSE:
                queue.writeData((byte)0x00, (byte)0);
                break;
            case KBD_CCMD_SELF_TEST:
                status = (byte)((status & ~KBD_STAT_OBF) | KBD_STAT_SELFTEST);
                queue.writeData((byte)0x55, (byte)0);
                break;
            case KBD_CCMD_KBD_TEST:
                queue.writeData((byte)0x00, (byte)0);
                break;
            case KBD_CCMD_KBD_DISABLE:
                mode |= KBD_MODE_DISABLE_KBD;
                updateIRQ();
                break;
            case KBD_CCMD_KBD_ENABLE:
                mode &= ~KBD_MODE_DISABLE_KBD;
                updateIRQ();
                break;
            case KBD_CCMD_READ_INPORT:
                queue.writeData((byte)0x00, (byte)0);
                break;
            case KBD_CCMD_READ_OUTPORT:
                /* XXX: check that */
                data = (byte)(0x01 | (physicalAddressSpace.getGateA20State() ? 0x02 : 0x00));
                if (0 != (status & KBD_STAT_OBF))
                    data |= 0x10;
                if (0 != (status & KBD_STAT_MOUSE_OBF))
                    data |= 0x20;
                queue.writeData(data, (byte)0);
                break;
            case KBD_CCMD_ENABLE_A20:
                setGateA20State(true);
                break;
            case KBD_CCMD_DISABLE_A20:
                setGateA20State(false);
                break;
            case KBD_CCMD_RESET:
                cpu.reset();
                break;
            case (byte)0xff:
                /* ignore that - I don't know what is its use */
                break;
            default:
                LOGGING.log(Level.INFO, "unsupported command 0x{0}", Integer.toHexString(0xff & data));
                break;
        }
    }

    private void writeKeyboard(byte data)
    {
        switch(keyboardWriteCommand) {
            default:
            case -1:
                switch(data) {
                    case 0x00:
                        queue.writeData(KBD_REPLY_ACK, (byte)0);
                        break;
                    case 0x05:
                        queue.writeData(KBD_REPLY_RESEND, (byte)0);
                        break;
                    case KBD_CMD_GET_ID:
                        synchronized (queue) {
                            queue.writeData(KBD_REPLY_ACK, (byte) 0);
                            queue.writeData((byte) 0xab, (byte) 0);
                            queue.writeData((byte) 0x83, (byte) 0);
                        }
                        break;
                    case KBD_CMD_ECHO:
                        queue.writeData(KBD_CMD_ECHO, (byte)0);
                        break;
                    case KBD_CMD_ENABLE:
                        keyboardScanEnabled = true;
                        queue.writeData(KBD_REPLY_ACK, (byte)0);
                        break;
                    case KBD_CMD_SET_LEDS:
                    case KBD_CMD_SET_RATE:
                        keyboardWriteCommand = data;
                        queue.writeData(KBD_REPLY_ACK, (byte)0);
                        break;
                    case KBD_CMD_RESET_DISABLE:
                        resetKeyboard();
                        keyboardScanEnabled = false;
                        queue.writeData(KBD_REPLY_ACK, (byte)0);
                        break;
                    case KBD_CMD_RESET_ENABLE:
                        resetKeyboard();
                        keyboardScanEnabled = true;
                        queue.writeData(KBD_REPLY_ACK, (byte)0);
                        break;
                    case KBD_CMD_RESET:
                        resetKeyboard();
                        synchronized (queue) {
                            queue.writeData(KBD_REPLY_ACK, (byte) 0);
                            queue.writeData(KBD_REPLY_POR, (byte) 0);
                        }
                        break;
                    default:
                        queue.writeData(KBD_REPLY_ACK, (byte)0);
                        break;
                }
                break;
            case KBD_CMD_SET_LEDS:
                queue.writeData(KBD_REPLY_ACK, (byte)0);
                keyboardWriteCommand = -1;
                break;
            case KBD_CMD_SET_RATE:
                queue.writeData(KBD_REPLY_ACK, (byte)0);
                keyboardWriteCommand = -1;
                break;
        }
    }


    private void writeMouse(byte data)
    {
        switch(mouseWriteCommand) {
            default:
            case -1:
                /* mouse command */
                if (mouseWrap) {
                    if (data == AUX_RESET_WRAP) {
                        mouseWrap = false;
                        queue.writeData(AUX_ACK, (byte)1);
                        return;
                    } else if (data != AUX_RESET) {
                        queue.writeData(data, (byte)1);
                        return;
                    }
                }
                switch(data) {
                    case AUX_SET_SCALE11:
                        mouseStatus &= ~MOUSE_STATUS_SCALE21;
                        queue.writeData(AUX_ACK, (byte)1);
                        break;
                    case AUX_SET_SCALE21:
                        mouseStatus |= MOUSE_STATUS_SCALE21;
                        queue.writeData(AUX_ACK, (byte)1);
                        break;
                    case AUX_SET_STREAM:
                        mouseStatus &= ~MOUSE_STATUS_REMOTE;
                        queue.writeData(AUX_ACK, (byte)1);
                        break;
                    case AUX_SET_WRAP:
                        mouseWrap = true;
                        queue.writeData(AUX_ACK, (byte)1);
                        break;
                    case AUX_SET_REMOTE:
                        mouseStatus |= MOUSE_STATUS_REMOTE;
                        queue.writeData(AUX_ACK, (byte)1);
                        break;
                    case AUX_GET_TYPE:
                        synchronized (queue) {
                            queue.writeData(AUX_ACK, (byte) 1);
                            queue.writeData((byte) MOUSE_TYPE, (byte) 1);
                        }
                        break;
                    case AUX_SET_RES:
                    case AUX_SET_SAMPLE:
                        mouseWriteCommand = data;
                        queue.writeData(AUX_ACK, (byte)1);
                        break;
                    case AUX_GET_SCALE:
                        synchronized (queue) {
                            queue.writeData(AUX_ACK, (byte) 1);
                            queue.writeData((byte) mouseStatus, (byte) 1);
                            queue.writeData((byte) mouseResolution, (byte) 1);
                            queue.writeData((byte) mouseSampleRate, (byte) 1);
                        }
                        break;
                    case AUX_POLL:
                        synchronized (queue) {
                            queue.writeData(AUX_ACK, (byte) 1);
                            mouseSendPacket();
                        }
                        break;
                    case AUX_ENABLE_DEV:
                        mouseStatus |= MOUSE_STATUS_ENABLED;
                        queue.writeData(AUX_ACK, (byte)1);
                        break;
                    case AUX_DISABLE_DEV:
                        mouseStatus &= ~MOUSE_STATUS_ENABLED;
                        queue.writeData(AUX_ACK, (byte)1);
                        break;
                    case AUX_SET_DEFAULT:
                        mouseSampleRate = 100;
                        mouseResolution = 2;
                        mouseStatus = 0;
                        queue.writeData(AUX_ACK, (byte)1);
                        break;
                    case AUX_RESET:
                        mouseSampleRate = 100;
                        mouseResolution = 2;
                        mouseStatus = 0;
                        synchronized (queue) {
                            queue.writeData(AUX_ACK, (byte) 1);
                            queue.writeData((byte) 0xaa, (byte) 1);
                            queue.writeData((byte) MOUSE_TYPE, (byte) 1);
                        }
                        break;
                    default:
                        break;
                }
                break;
            case AUX_SET_SAMPLE:
                mouseSampleRate = data;
                queue.writeData(AUX_ACK, (byte)1);
                mouseWriteCommand = -1;
                break;
            case AUX_SET_RES:
                mouseResolution = data;
                queue.writeData(AUX_ACK, (byte)1);
                mouseWriteCommand = -1;
                break;
        }
    }

    private void resetKeyboard()
    {
        keyboardScanEnabled = true;
    }

    private void mouseSendPacket()
    {
        int dx1 = mouseDx;
        int dy1 = mouseDy;
        int dz1 = mouseDz;
        /* XXX: increase range to 8 bits ? */
        if (dx1 > 127)
            dx1 = 127;
        else if (dx1 < -127)
            dx1 = -127;
        if (dy1 > 127)
            dy1 = 127;
        else if (dy1 < -127)
            dy1 = -127;
        int x = 0;
        int y = 0;
        if (dx1 < 0)
            x = 1;
        if (dy1 < 0)
            y = 1;
        byte b = (byte)(0x08 | (x << 4) | (y << 5) | (mouseButtons & 0x07));

        synchronized (queue) {
            queue.writeData(b, (byte) 1);
            queue.writeData((byte) dx1, (byte) 1);
            queue.writeData((byte) dy1, (byte) 1);
            /* extra byte for IMPS/2 or IMEX */
            switch (MOUSE_TYPE) {
                default:
                    break;
                case 3:
                    if (dz1 > 127)
                        dz1 = 127;
                    else if (dz1 < -127)
                        dz1 = -127;
                    queue.writeData((byte) dz1, (byte) 1);
                    break;
                case 4:
                    if (dz1 > 7)
                        dz1 = 7;
                    else if (dz1 < -7)
                        dz1 = -7;
                    b = (byte) ((dz1 & 0x0f) | ((mouseButtons & 0x18) << 1));
                    queue.writeData(b, (byte) 1);
                    break;
            }
        }
        /* update deltas */
        mouseDx -= dx1;
        mouseDy -= dy1;
        mouseDz -= dz1;
    }

    private void updateIRQ()
    {
        int irq1Level = 0;
        int irq12Level = 0;
        status = (byte)(status & ~(KBD_STAT_OBF | KBD_STAT_MOUSE_OBF));
        synchronized (queue) {
            if (queue.length != 0) {
                status = (byte) (status | KBD_STAT_OBF);
                if (0 != queue.getAux()) {
                    status = (byte) (status | KBD_STAT_MOUSE_OBF);
                    if (0 != (mode & KBD_MODE_MOUSE_INT))
                        if (!Option.useBochs.isSet())
                            irq12Level = 1;
                } else
                if ((0 != (mode & KBD_MODE_KBD_INT)) &&
                        (0 == (mode & KBD_MODE_DISABLE_KBD)))
                    irq1Level = 1;
            }
        }
        irqDevice.setIRQ(1, irq1Level);
        irqDevice.setIRQ(12, irq12Level);
    }

    private class KeyboardQueue implements Hibernatable
    {
        private byte[] aux;
        private byte[] data;
        private int readPosition;
        private int writePosition;
        private int length;

        public KeyboardQueue()
        {
            aux = new byte[KBD_QUEUE_SIZE];
            data = new byte[KBD_QUEUE_SIZE];
            readPosition = 0;
            writePosition = 0;
            length = 0;
        }

        public void saveState(DataOutput output) throws IOException
        {
            output.writeInt(aux.length);
            output.write(aux);
            output.writeInt(data.length);
            output.write(data);
            output.writeInt(readPosition);
            output.writeInt(writePosition);
            output.writeInt(length);
        }

        public void loadState(DataInput input) throws IOException
        {
            int len = input.readInt();
            aux = new byte[len];
            input.readFully(aux,0,len);
            len = input.readInt();
            data = new byte[len];
            input.readFully(data,0,len);
            readPosition = input.readInt();
            writePosition = input.readInt();
            length = input.readInt();
        }

        public void reset()
        {
            synchronized (this) {
                readPosition = 0;
                writePosition = 0;
                length = 0;
            }
        }

        public byte getAux()
        {
            synchronized (this) {
                return aux[readPosition];
            }           
        }

        public byte readData()
        {
            synchronized (this) {
                if (length == 0) {
                    /* NOTE: if no data left, we return the last keyboard one (needed for EMM386) */
                    /* XXX: need a timer to do things correctly */
                    int index = readPosition - 1;
                    if (index < 0)
                        index = KBD_QUEUE_SIZE - 1;
                    return data[index];
                }
                byte auxValue = this.aux[readPosition];
                byte dataValue = this.data[readPosition];
                if ((++readPosition) == KBD_QUEUE_SIZE)
                    readPosition = 0;
                length--;
                /* reading deasserts IRQ */
                if (0 != auxValue)
                    Keyboard.this.irqDevice.setIRQ(12, 0);
                else
                    Keyboard.this.irqDevice.setIRQ(1, 0);
                return dataValue;
            }
        }

        public void writeData(byte data, byte aux)
        {
            synchronized (this) {
                if (length >= KBD_QUEUE_SIZE)
                {
                    LOGGING.log(Level.INFO, "Keyboard buffer full, ignoring keycode: "+data);
                    return;
                }
                this.aux[writePosition] = aux;
                this.data[writePosition] = data;
                if ((++writePosition) == KBD_QUEUE_SIZE)
                    writePosition = 0;
                length++;
            }
            Keyboard.this.updateIRQ();
        }
    }

    public void keyPressed(byte scancode)
    {
        switch (scancode)
        {
            case (byte)0xff:
                synchronized (queue) {
                    putKeyboardEvent((byte) 0xe1);
                    putKeyboardEvent((byte) 0x1d);
                    putKeyboardEvent((byte) 0x45);
                    putKeyboardEvent((byte) 0xe1);
                    putKeyboardEvent((byte) 0x9d);
                    putKeyboardEvent((byte) 0xc5);
                }
                return;
            default:
                synchronized (queue) {
                    if (scancode < 0)
                        putKeyboardEvent((byte) 0xe0);
                    putKeyboardEvent((byte) (scancode & 0x7f));
                }
                return;
        }
    }
    public void keyReleased(byte scancode)
    {
        synchronized (queue) {
            if (scancode < 0)
                putKeyboardEvent((byte) 0xe0);
            putKeyboardEvent((byte) (scancode | 0x80));
        }
    }

    private void putKeyboardEvent(byte keycode)
    {
        queue.writeData(keycode, (byte)0);
    }

    public void putMouseEvent(int dx, int dy, int dz, int buttons)
    {
        if (0 == (mouseStatus & MOUSE_STATUS_ENABLED))
            return;

        mouseDx += dx;
        mouseDy -= dy;
        mouseDz += dz;

        mouseButtons = buttons;

        synchronized (queue) {
            if ((0 == (mouseStatus & MOUSE_STATUS_REMOTE)) && (queue.length < (KBD_QUEUE_SIZE - 16)))
                while (true) {
                    /* if not remote, send event.  Multiple events are sent if too big deltas */
                    mouseSendPacket();
                    if (mouseDx == 0 && mouseDy == 0 && mouseDz == 0)
                        break;
                }
        }
    }

    public boolean initialised()
    {
        return ioportRegistered && (irqDevice != null) && (cpu != null) && (physicalAddressSpace != null) && (linearAddressSpace != null);
    }

    public boolean updated()
    {
        return ioportRegistered && irqDevice.updated() && cpu.updated() && physicalAddressSpace.updated() && linearAddressSpace.updated();
    }

    public void updateComponent(HardwareComponent component)
    {
        if ((component instanceof IOPortHandler) && component.updated())
        {
            ((IOPortHandler)component).registerIOPortCapable(this);
            ioportRegistered = true;
        }
    }

    public void acceptComponent(HardwareComponent component)
    {
        if ((component instanceof InterruptController) && component.initialised())
            irqDevice = (InterruptController)component;

        if ((component instanceof IOPortHandler) && component.initialised())
        {
            ((IOPortHandler)component).registerIOPortCapable(this);
            ioportRegistered = true;
        }

        if ((component instanceof Processor) && component.initialised())
            cpu = (Processor)component;

        if (component instanceof PhysicalAddressSpace)
            physicalAddressSpace = (PhysicalAddressSpace) component;

        if (component instanceof LinearAddressSpace)
            linearAddressSpace = (LinearAddressSpace) component;
    }
}