package org.jpc.emulator.peripheral;

import org.jpc.emulator.AbstractHardwareComponent;
import org.jpc.emulator.HardwareComponent;
import org.jpc.emulator.Timer;
import org.jpc.emulator.TimerResponsive;
import org.jpc.emulator.motherboard.IODevice;
import org.jpc.emulator.motherboard.IOPortHandler;
import org.jpc.emulator.motherboard.InterruptController;
import org.jpc.j2se.Option;
import org.jpc.support.Clock;

import java.util.logging.*;

public class MPU401 extends AbstractHardwareComponent implements IODevice
{
    private static final Logger Log = Logger.getLogger(MPU401.class.getName());
    private static InterruptController irqDevice;
    private static Clock timeSource;
    private static Timer event;
    private static boolean ioportRegistered = false;

    static final private int MPU401_VERSION = 0x15;
    static final private int MPU401_REVISION = 0x01;
    static final private int MPU401_QUEUE = 32;
    static final private float MPU401_TIMECONSTANT = (60000000/1000.0f);
    static final private float MPU401_RESETBUSY = 27.0f;

    static final private int M_UART = 0;
    static final private int M_INTELLIGENT = 1;

    static final private int T_OVERFLOW = 0;
    static final private int T_MARK = 1;
    static final private int T_MIDI_SYS = 2;
    static final private int T_MIDI_NORM = 3;
    static final private int T_COMMAND = 4;

    /* Messages sent to MPU-401 from host */
    static final private int MSG_EOX = 0xf7;
    static final private int MSG_OVERFLOW = 0xf8;
    static final private int MSG_MARK = 0xfc;

    /* Messages sent to host from MPU-401 */
    static final private int MSG_MPU_OVERFLOW = 0xf8;
    static final private int MSG_MPU_COMMAND_REQ = 0xf9;
    static final private int MSG_MPU_END = 0xfc;
    static final private int MSG_MPU_CLOCK = 0xfd;
    static final private int MSG_MPU_ACK = 0xfe;

    static final private class MPU {
        public MPU() {
            for (int i=0;i<playbuf.length;i++)
                playbuf[i] = new track();
        }
        public boolean intelligent;
        public int mode;
        /*Bitu*/int irq;
        /*Bit8u*/short[] queue = new short[MPU401_QUEUE];
        /*Bitu*/int queue_pos,queue_used;
        public static class track {
            /*Bits*/int counter;
            /*Bit8u*/short[] value = new short[8];
            short sys_val;
            /*Bit8u*/short vlength,length;
            /*MpuDataType*/ int type;
        }
        public final track[] playbuf = new track[8];
        public final track condbuf = new track();

        public static class State {
            boolean conductor,cond_req,cond_set, block_ack;
            boolean playing,reset;
            boolean wsd,wsm,wsd_start;
            boolean run_irq,irq_pending;
            boolean send_now;
            boolean eoi_scheduled;
            /*Bits*/int data_onoff;
            /*Bitu*/int command_byte, cmd_pending;
            /*Bit8u*/short tmask,cmask,amask;
            /*Bit16u*/int midi_mask;
            /*Bit16u*/int req_mask;
            /*Bit8u*/short channel,old_chan;
        }
        public final State state = new State();

        public static class Clock {
            /*Bit8u*/short timebase,old_timebase;
            /*Bit8u*/short tempo,old_tempo;
            /*Bit8u*/short tempo_rel,old_tempo_rel;
            /*Bit8u*/short tempo_grad;
            /*Bit8u*/short cth_rate,cth_counter;
            boolean clock_to_host,cth_active;
        }
        public final Clock clock = new Clock();
    }
    private static final MPU mpu = new MPU();

    private static void QueueByte(/*Bit8u*/int data) {
        if (mpu.state.block_ack) {mpu.state.block_ack=false;return;}
        if (mpu.queue_used==0 && mpu.intelligent) {
            mpu.state.irq_pending=true;
            //Pic.PIC_ActivateIRQ(mpu.irq);
            irqDevice.setIRQ(mpu.irq, 1);
        }
        if (mpu.queue_used<MPU401_QUEUE) {
            /*Bitu*/int pos=mpu.queue_used+mpu.queue_pos;
            if (mpu.queue_pos>=MPU401_QUEUE) mpu.queue_pos-=MPU401_QUEUE;
            if (pos>=MPU401_QUEUE) pos-=MPU401_QUEUE;
            mpu.queue_used++;
            mpu.queue[pos]=(short)data;
        } else Log.log(Level.FINE,"MPU401:Data queue full");
    }

    private static void ClrQueue() {
        mpu.queue_used=0;
        mpu.queue_pos=0;
    }

    private static final int MPU401_ReadStatus(/*Bitu*/int port, /*Bitu*/int iolen) {
            /*Bit8u*/short ret=0x3f;	/* Bits 6 and 7 clear */
            if (mpu.state.cmd_pending!=0) ret|=0x40;
            if (mpu.queue_used==0) ret|=0x80;
            return ret;
        }

    private static final void MPU401_WriteCommand(/*Bitu*/int port, /*Bitu*/int val, /*Bitu*/int iolen) {
            if (mpu.state.reset) {mpu.state.cmd_pending=val+1;return;}
            if (val<=0x2f) {
                switch (val&3) { /* MIDI stop, start, continue */
                    case 1: {Midi.MIDI_RawOutByte(0xfc);break;}
                    case 2: {Midi.MIDI_RawOutByte(0xfa);break;}
                    case 3: {Midi.MIDI_RawOutByte(0xfb);break;}
                }
                if ((val&0x20)!=0) Log.log(Level.SEVERE,"MPU-401:Unhandled Recording Command "+Integer.toString(val,16));
                switch (val&0xc) {
                    case  0x4:	/* Stop */
                        //Pic.PIC_RemoveEvents(MPU401_Event);
                        mpu.state.playing=false;
                        for (/*Bitu*/int i=0xb0;i<0xbf;i++) {	/* All notes off */
                            Midi.MIDI_RawOutByte(i);
                            Midi.MIDI_RawOutByte(0x7b);
                            Midi.MIDI_RawOutByte(0);
                        }
                        break;
                    case 0x8:	/* Play */
                        Log.log(Level.INFO,"MPU-401:Intelligent mode playback started");
                        mpu.state.playing=true;
                        //Pic.PIC_RemoveEvents(MPU401_Event);
                        //Pic.PIC_AddEvent(MPU401_Event,MPU401_TIMECONSTANT/(mpu.clock.tempo*mpu.clock.timebase));
                        event.setExpiry((long)(timeSource.getRealMillis()+MPU401_TIMECONSTANT/(mpu.clock.tempo*mpu.clock.timebase)));
                        ClrQueue();
                        break;
                }
            }
            else if (val>=0xa0 && val<=0xa7) {	/* Request play counter */
                if ((mpu.state.cmask & (1<<(val&7)))!=0) QueueByte(mpu.playbuf[val&7].counter);
            }
            else if (val>=0xd0 && val<=0xd7) {	/* Send data */
                mpu.state.old_chan=mpu.state.channel;
                mpu.state.channel=(short)(val&7);
                mpu.state.wsd=true;
                mpu.state.wsm=false;
                mpu.state.wsd_start=true;
            }
            else
            switch (val) {
                case 0xdf:	/* Send system message */
                    mpu.state.wsd=false;
                    mpu.state.wsm=true;
                    mpu.state.wsd_start=true;
                    break;
                case 0x8e:	/* Conductor */
                    mpu.state.cond_set=false;
                    break;
                case 0x8f:
                    mpu.state.cond_set=true;
                    break;
                case 0x94: /* Clock to host */
                    mpu.clock.clock_to_host=false;
                    break;
                case 0x95:
                    mpu.clock.clock_to_host=true;
                    break;
                case 0xc2: /* Internal timebase */
                    mpu.clock.timebase=48;
                    break;
                case 0xc3:
                    mpu.clock.timebase=72;
                    break;
                case 0xc4:
                    mpu.clock.timebase=96;
                    break;
                case 0xc5:
                    mpu.clock.timebase=120;
                    break;
                case 0xc6:
                    mpu.clock.timebase=144;
                    break;
                case 0xc7:
                    mpu.clock.timebase=168;
                    break;
                case 0xc8:
                    mpu.clock.timebase=192;
                    break;
                /* Commands with data byte */
                case 0xe0: case 0xe1: case 0xe2: case 0xe4: case 0xe6:
                case 0xe7: case 0xec: case 0xed: case 0xee: case 0xef:
                    mpu.state.command_byte=val;
                    break;
                /* Commands 0xa# returning data */
                case 0xab:	/* Request and clear recording counter */
                    QueueByte(MSG_MPU_ACK);
                    QueueByte(0);
                    return;
                case 0xac:	/* Request version */
                    QueueByte(MSG_MPU_ACK);
                    QueueByte(MPU401_VERSION);
                    return;
                case 0xad:	/* Request revision */
                    QueueByte(MSG_MPU_ACK);
                    QueueByte(MPU401_REVISION);
                    return;
                case 0xaf:	/* Request tempo */
                    QueueByte(MSG_MPU_ACK);
                    QueueByte(mpu.clock.tempo);
                    return;
                case 0xb1:	/* Reset relative tempo */
                    mpu.clock.tempo_rel=40;
                    break;
                case 0xb9:	/* Clear play map */
                case 0xb8:	/* Clear play counters */
                    for (/*Bitu*/int i=0xb0;i<0xbf;i++) {	/* All notes off */
                        Midi.MIDI_RawOutByte(i);
                        Midi.MIDI_RawOutByte(0x7b);
                        Midi.MIDI_RawOutByte(0);
                    }
                    for (/*Bitu*/int i=0;i<8;i++) {
                        mpu.playbuf[i].counter=0;
                        mpu.playbuf[i].type=T_OVERFLOW;
                    }
                    mpu.condbuf.counter=0;
                    mpu.condbuf.type=T_OVERFLOW;
                    if (!(mpu.state.conductor=mpu.state.cond_set)) mpu.state.cond_req=false;
                    mpu.state.amask=mpu.state.tmask;
                    mpu.state.req_mask=0;
                    mpu.state.irq_pending=true;
                    break;
                case 0xff:	/* Reset MPU-401 */
                    Log.log(Level.INFO,"MPU-401:Reset "+Integer.toString(val,16));
                    //Pic.PIC_AddEvent(MPU401_ResetDone,MPU401_RESETBUSY);
                    MPU401_ResetDone.callback();
			        mpu.state.reset=true;
                    MPU401_Reset();
                    if (mpu.mode==M_UART) return;//do not send ack in UART mode
                    break;
                case 0x3f:	/* UART mode */
                    Log.log(Level.INFO,"MPU-401:Set UART mode "+Integer.toString(val,16));
                    mpu.mode=M_UART;
                    break;
                default:
                    //Log.log(LogType.LOG_MISC, LogSeverity.LOG_NORMALS,"MPU-401:Unhandled command %X",val);
            }
            QueueByte(MSG_MPU_ACK);
        }

    static private final int MPU401_ReadData(/*Bitu*/int port, /*Bitu*/int iolen) {
            /*Bit8u*/short ret=MSG_MPU_ACK;
            if (mpu.queue_used!=0) {
                if (mpu.queue_pos>=MPU401_QUEUE) mpu.queue_pos-=MPU401_QUEUE;
                ret=mpu.queue[mpu.queue_pos];
                mpu.queue_pos++;mpu.queue_used--;
            }
            if (!mpu.intelligent) return ret;

            if (mpu.queue_used == 0) irqDevice.setIRQ(mpu.irq, 0);//Pic.PIC_DeActivateIRQ(mpu.irq);

            if (ret>=0xf0 && ret<=0xf7) { /* MIDI data request */
                mpu.state.channel=(short)(ret&7);
                mpu.state.data_onoff=0;
                mpu.state.cond_req=false;
            }
            if (ret==MSG_MPU_COMMAND_REQ) {
                mpu.state.data_onoff=0;
                mpu.state.cond_req=true;
                if (mpu.condbuf.type!=T_OVERFLOW) {
                    mpu.state.block_ack=true;
                    MPU401_WriteCommand(0x331,mpu.condbuf.value[0],1);
                    if (mpu.state.command_byte!=0) MPU401_WriteData(0x330,mpu.condbuf.value[1],1);
                }
            mpu.condbuf.type=T_OVERFLOW;
            }
            if (ret==MSG_MPU_END || ret==MSG_MPU_CLOCK || ret==MSG_MPU_ACK) {
                mpu.state.data_onoff=-1;
                MPU401_EOIHandlerDispatch();
            }
            return ret;
        }

    private static /*Bitu*/int length,cnt,posd;
    private static final void MPU401_WriteData(/*Bitu*/int port, /*Bitu*/int val, /*Bitu*/int iolen) {
            if (mpu.mode==M_UART) {Midi.MIDI_RawOutByte(val);return;}
            switch (mpu.state.command_byte) {	/* 0xe# command data */
                case 0x00:
                    break;
                case 0xe0:	/* Set tempo */
                    mpu.state.command_byte=0;
                    mpu.clock.tempo=(short)val;
                    return;
                case 0xe1:	/* Set relative tempo */
                    mpu.state.command_byte=0;
                    if (val!=0x40) //default value
                        Log.log(Level.SEVERE,"MPU-401:Relative tempo change not implemented");
                    return;
                case 0xe7:	/* Set internal clock to host interval */
                    mpu.state.command_byte=0;
                    mpu.clock.cth_rate=(short)(val>>2);
                    return;
                case 0xec:	/* Set active track mask */
                    mpu.state.command_byte=0;
                    mpu.state.tmask=(short)val;
                    return;
                case 0xed: /* Set play counter mask */
                    mpu.state.command_byte=0;
                    mpu.state.cmask=(short)val;
                    return;
                case 0xee: /* Set 1-8 MIDI channel mask */
                    mpu.state.command_byte=0;
                    mpu.state.midi_mask&=0xff00;
                    mpu.state.midi_mask|=val;
                    return;
                case 0xef: /* Set 9-16 MIDI channel mask */
                    mpu.state.command_byte=0;
                    mpu.state.midi_mask&=0x00ff;
                    mpu.state.midi_mask|=((/*Bit16u*/int)val)<<8;
                    return;
                //case 0xe2:	/* Set graduation for relative tempo */
                //case 0xe4:	/* Set metronome */
                //case 0xe6:	/* Set metronome measure length */
                default:
                    mpu.state.command_byte=0;
                    return;
            }
            if (mpu.state.wsd) {	/* Directly send MIDI message */
                if (mpu.state.wsd_start) {
                    mpu.state.wsd_start=false;
                    cnt=0;
                        switch (val&0xf0) {
                            case 0xc0:case 0xd0:
                                mpu.playbuf[mpu.state.channel].value[0]=(short)val;
                                length=2;
                                break;
                            case 0x80:case 0x90:case 0xa0:case 0xb0:case 0xe0:
                                mpu.playbuf[mpu.state.channel].value[0]=(short)val;
                                length=3;
                                break;
                            case 0xf0:
                                Log.log(Level.SEVERE,"MPU-401:Illegal WSD byte");
                                mpu.state.wsd=false;
                                mpu.state.channel=mpu.state.old_chan;
                                return;
                            default: /* MIDI with running status */
                                cnt++;
                                Midi.MIDI_RawOutByte(mpu.playbuf[mpu.state.channel].value[0]);
                        }
                }
                if (cnt<length) {Midi.MIDI_RawOutByte(val);cnt++;}
                if (cnt==length) {
                    mpu.state.wsd=false;
                    mpu.state.channel=mpu.state.old_chan;
                }
                return;
            }
            if (mpu.state.wsm) {	/* Directly send system message */
                if (val==MSG_EOX) {Midi.MIDI_RawOutByte(MSG_EOX);mpu.state.wsm=false;return;}
                if (mpu.state.wsd_start) {
                    mpu.state.wsd_start=false;
                    cnt=0;
                    switch (val) {
                        case 0xf2:{ length=3; break;}
                        case 0xf3:{ length=2; break;}
                        case 0xf6:{ length=1; break;}
                        case 0xf0:{ length=0; break;}
                        default:
                            length=0;
                    }
                }
                if (length==0 || cnt<length) {Midi.MIDI_RawOutByte(val);cnt++;}
                if (cnt==length) mpu.state.wsm=false;
                return;
            }
            if (mpu.state.cond_req) { /* Command */
                switch (mpu.state.data_onoff) {
                    case -1:
                        return;
                    case  0: /* Timing byte */
                        mpu.condbuf.vlength=0;
                        if (val<0xf0) mpu.state.data_onoff++;
                        else {
                            mpu.state.data_onoff=-1;
                            MPU401_EOIHandlerDispatch();
                            return;
                        }
                        if (val==0) mpu.state.send_now=true;
                        else mpu.state.send_now=false;
                        mpu.condbuf.counter=val;
                        break;
                    case  1: /* Command byte #1 */
                        mpu.condbuf.type=T_COMMAND;
                        if (val==0xf8 || val==0xf9) mpu.condbuf.type=T_OVERFLOW;
                        mpu.condbuf.value[mpu.condbuf.vlength]=(short)val;
                        mpu.condbuf.vlength++;
                        if ((val&0xf0)!=0xe0) MPU401_EOIHandlerDispatch();
                        else mpu.state.data_onoff++;
                        break;
                    case  2:/* Command byte #2 */
                        mpu.condbuf.value[mpu.condbuf.vlength]=(short)val;
                        mpu.condbuf.vlength++;
                        MPU401_EOIHandlerDispatch();
                        break;
                }
                return;
            }
            switch (mpu.state.data_onoff) { /* Data */
                case   -1:
                    return;
                case    0: /* Timing byte */
                    if (val<0xf0) mpu.state.data_onoff=1;
                    else {
                        mpu.state.data_onoff=-1;
                        MPU401_EOIHandlerDispatch();
                        return;
                    }
                    if (val==0) mpu.state.send_now=true;
                    else mpu.state.send_now=false;
                    mpu.playbuf[mpu.state.channel].counter=val;
                    break;
                case    1: /* MIDI */
                    mpu.playbuf[mpu.state.channel].vlength++;
                    posd=mpu.playbuf[mpu.state.channel].vlength;
                    if (posd==1) {
                        switch (val&0xf0) {
                            case 0xf0: /* System message or mark */
                                if (val>0xf7) {
                                    mpu.playbuf[mpu.state.channel].type=T_MARK;
                                    mpu.playbuf[mpu.state.channel].sys_val=(short)val;
                                    length=1;
                                } else {
                                    Log.log(Level.SEVERE,"MPU-401:Illegal message");
                                    mpu.playbuf[mpu.state.channel].type=T_MIDI_SYS;
                                    mpu.playbuf[mpu.state.channel].sys_val=(short)val;
                                    length=1;
                                }
                                break;
                            case 0xc0: case 0xd0: /* MIDI Message */
                                mpu.playbuf[mpu.state.channel].type=T_MIDI_NORM;
                                length=mpu.playbuf[mpu.state.channel].length=2;
                                break;
                            case 0x80: case 0x90: case 0xa0:  case 0xb0: case 0xe0:
                                mpu.playbuf[mpu.state.channel].type=T_MIDI_NORM;
                                length=mpu.playbuf[mpu.state.channel].length=3;
                                break;
                            default: /* MIDI data with running status */
                                posd++;
                                mpu.playbuf[mpu.state.channel].vlength++;
                                mpu.playbuf[mpu.state.channel].type=T_MIDI_NORM;
                                length=mpu.playbuf[mpu.state.channel].length;
                                break;
                        }
                    }
                    if (!(posd==1 && val>=0xf0)) mpu.playbuf[mpu.state.channel].value[posd-1]=(short)val;
                    if (posd==length) MPU401_EOIHandlerDispatch();
            }
        }

    private static void MPU401_IntelligentOut(/*Bit8u*/int chan) {
        /*Bitu*/int val;
        switch (mpu.playbuf[chan].type) {
            case T_OVERFLOW:
                break;
            case T_MARK:
                val=mpu.playbuf[chan].sys_val;
                if (val==0xfc) {
                    Midi.MIDI_RawOutByte(val);
                    mpu.state.amask&=~(1<<chan);
                    mpu.state.req_mask&=~(1<<chan);
                }
                break;
            case T_MIDI_NORM:
                for (/*Bitu*/int i=0;i<mpu.playbuf[chan].vlength;i++)
                    Midi.MIDI_RawOutByte(mpu.playbuf[chan].value[i]);
                break;
            default:
                break;
        }
    }

    private static void UpdateTrack(/*Bit8u*/int chan) {
        MPU401_IntelligentOut(chan);
        if ((mpu.state.amask & (1<<chan))!=0) {
            mpu.playbuf[chan].vlength=0;
            mpu.playbuf[chan].type=T_OVERFLOW;
            mpu.playbuf[chan].counter=0xf0;
            mpu.state.req_mask|=(1<<chan);
        } else {
            if (mpu.state.amask==0 && !mpu.state.conductor) mpu.state.req_mask|=(1<<12);
        }
    }

    private static void UpdateConductor() {
        if (mpu.condbuf.value[0]==0xfc) {
            mpu.condbuf.value[0]=0;
            mpu.state.conductor=false;
            mpu.state.req_mask&=~(1<<9);
            if (mpu.state.amask==0) mpu.state.req_mask|=(1<<12);
            return;
        }
        mpu.condbuf.vlength=0;
        mpu.condbuf.counter=0xf0;
        mpu.state.req_mask|=(1<<9);
    }

    static private final TimerResponsive MPU401_Event = new TimerResponsive() {
        public void callback() {
            if (mpu.mode==M_UART) return;
            if (!mpu.state.irq_pending) {
                for (/*Bitu*/int i=0;i<8;i++) { /* Decrease counters */
                    if ((mpu.state.amask & (1<<i))!=0) {
                        mpu.playbuf[i].counter--;
                        if (mpu.playbuf[i].counter<=0) UpdateTrack(i);
                    }
                }
                if (mpu.state.conductor) {
                    mpu.condbuf.counter--;
                    if (mpu.condbuf.counter<=0) UpdateConductor();
                }
                if (mpu.clock.clock_to_host) {
                    mpu.clock.cth_counter++;
                    if (mpu.clock.cth_counter >= mpu.clock.cth_rate) {
                        mpu.clock.cth_counter=0;
                        mpu.state.req_mask|=(1<<13);
                    }
                }
                if (!mpu.state.irq_pending && mpu.state.req_mask!=0) MPU401_EOIHandler.callback();
            }
            //Pic.PIC_RemoveEvents(MPU401_Event);
            /*Bitu*/int new_time;
            if ((new_time=mpu.clock.tempo*mpu.clock.timebase)==0) return;
            //Pic.PIC_AddEvent(MPU401_Event,MPU401_TIMECONSTANT/new_time);
            event.setExpiry((long)(timeSource.getEmulatedNanos()+MPU401_TIMECONSTANT/new_time));
        }
        public int getType() {return -1;}
    };

    private static void MPU401_EOIHandlerDispatch() {
        if (mpu.state.send_now) {
            mpu.state.eoi_scheduled=true;
            //Pic.PIC_AddEvent(MPU401_EOIHandler,0.06f); //Possible a bit longer
            MPU401_EOIHandler.callback();
        }
        else if (!mpu.state.eoi_scheduled) MPU401_EOIHandler.callback();
    }

    //Updates counters and requests new data on "End of Input"
    static private final TimerResponsive MPU401_EOIHandler = new TimerResponsive() {
        public void callback() {
            mpu.state.eoi_scheduled=false;
            if (mpu.state.send_now) {
                mpu.state.send_now=false;
                if (mpu.state.cond_req) UpdateConductor();
                else UpdateTrack(mpu.state.channel);
            }
            mpu.state.irq_pending=false;
            if (!mpu.state.playing || mpu.state.req_mask==0) return;
            /*Bitu*/int i=0;
            do {
                if ((mpu.state.req_mask & (1<<i))!=0) {
                    QueueByte(0xf0+i);
                    mpu.state.req_mask&=~(1<<i);
                    break;
                }
            } while ((i++)<16);
        }
        public int getType() {return -1;}
    };

    static private final TimerResponsive MPU401_ResetDone = new TimerResponsive() {
        public void callback() {
            mpu.state.reset=false;
            if (mpu.state.cmd_pending!=0) {
                MPU401_WriteCommand(0x331,mpu.state.cmd_pending-1,1);
                mpu.state.cmd_pending=0;
            }
        }
        public int getType() {return -1;}
    };

    private static void MPU401_Reset() {
        //Pic.PIC_DeActivateIRQ(mpu.irq);
        irqDevice.setIRQ(mpu.irq, 0);
        mpu.mode=(mpu.intelligent ? M_INTELLIGENT : M_UART);
        //Pic.PIC_RemoveEvents(MPU401_EOIHandler);
	    mpu.state.eoi_scheduled=false;
        mpu.state.wsd=false;
        mpu.state.wsm=false;
        mpu.state.conductor=false;
        mpu.state.cond_req=false;
        mpu.state.cond_set=false;
        mpu.state.playing=false;
        mpu.state.run_irq=false;
        mpu.state.irq_pending=false;
        mpu.state.cmask=0xff;
        mpu.state.amask=mpu.state.tmask=0;
        mpu.state.midi_mask=0xffff;
        mpu.state.data_onoff=0;
        mpu.state.command_byte=0;
        mpu.state.block_ack=false;
        mpu.clock.tempo=mpu.clock.old_tempo=100;
        mpu.clock.timebase=mpu.clock.old_timebase=120;
        mpu.clock.tempo_rel=mpu.clock.old_tempo_rel=40;
        mpu.clock.tempo_grad=0;
        mpu.clock.clock_to_host=false;
        mpu.clock.cth_rate=60;
        mpu.clock.cth_counter=0;
        ClrQueue();
        mpu.state.req_mask=0;
        mpu.condbuf.counter=0;
        mpu.condbuf.type=T_OVERFLOW;
        for (/*Bitu*/int i=0;i<8;i++) {mpu.playbuf[i].type=T_OVERFLOW;mpu.playbuf[i].counter=0;}
    }

//    private IoHandler.IO_ReadHandleObject[] ReadHandler=new IoHandler.IO_ReadHandleObject[2];
//    private IoHandler.IO_WriteHandleObject[] WriteHandler=new IoHandler.IO_WriteHandleObject[2];
    private boolean installed; /*as it can fail to install by 2 ways (config and no midi)*/

    public MPU401() {
        installed = false;
        String s_mpu = Option.mpu401.value("intelligent");
        if(s_mpu.equalsIgnoreCase("none")) return;
        if(s_mpu.equalsIgnoreCase("off")) return;
        if(s_mpu.equalsIgnoreCase("false")) return;
        if (!Midi.MIDI_Available()) return;
        /*Enabled and there is a Midi */
        installed = true;

//        for (int i=0;i<WriteHandler.length;i++) {
//            WriteHandler[i] = new IoHandler.IO_WriteHandleObject();
//        }
//        for (int i=0;i<ReadHandler.length;i++) {
//            ReadHandler[i] = new IoHandler.IO_ReadHandleObject();
//        }
//        WriteHandler[0].Install(0x330,MPU401_WriteData,IoHandler.IO_MB);
//        WriteHandler[1].Install(0x331,MPU401_WriteCommand,IoHandler.IO_MB);
//        ReadHandler[0].Install(0x330,MPU401_ReadData,IoHandler.IO_MB);
//        ReadHandler[1].Install(0x331,MPU401_ReadStatus,IoHandler.IO_MB);

        mpu.queue_used=0;
        mpu.queue_pos=0;
        mpu.mode=M_UART;
        mpu.irq=9;	/* Princess Maker 2 wants it on irq 9 */

        mpu.intelligent = true;	//Default is on
        if(s_mpu.equalsIgnoreCase("uart")) mpu.intelligent = false;
        if (!mpu.intelligent) return;
        /*Set IRQ and unmask it(for timequest/princess maker 2) */
        //Pic.PIC_SetIRQMask(mpu.irq,false);
        //MPU401_Reset();
    }

    public int ioPortRead8(int address)
    {
        return ioPortRead32(address);
    }

    public int ioPortRead16(int address)
    {
        return ioPortRead32(address);
    }

    public int ioPortRead32(int port)
    {
        if (port == 0x330)
        {
            return MPU401_ReadData(port, 32);
        }
        else if (port == 0x331)
        {
            return MPU401_ReadStatus(port, 32);
        }
        throw new IllegalStateException("Unknown port read "+port);
    }

    public void ioPortWrite8(int address, int data)
    {
        ioPortWrite32(address, data);
    }

    public void ioPortWrite16(int address, int data)
    {
        ioPortWrite32(address, data);
    }

    public void ioPortWrite32(int port, int data)
    {
        if (port == 0x330)
        {
            MPU401_WriteData(port, data, 32);
        }
        else if (port == 0x331)
        {
            MPU401_WriteCommand(port, data, 32);
        }
    }

    public int[] ioPortsRequested()
    {
        int[] ports = new int[]{0x330, 0x331};
        return ports;
    }

    public boolean initialised()
    {
        return (irqDevice != null) && (timeSource != null) && ioportRegistered;
    }

    public boolean updated()
    {
        return (irqDevice.updated() && timeSource.updated());
    }

    public void acceptComponent(HardwareComponent component)
    {
        if ((component instanceof InterruptController) && component.initialised())
            irqDevice = (InterruptController) component;
        if ((component instanceof Clock) && component.initialised())
            timeSource = (Clock) component;
        if ((component instanceof IOPortHandler) && component.initialised()) {
            ((IOPortHandler) component).registerIOPortCapable(this);
            ioportRegistered = true;
        }

        if (this.initialised())
            MPU401_Init();
    }

    static private MPU401 test;

    public static void MPU401_Destroy() {
        if(!test.installed) return;
        if(!Option.mpu401.value("intelligent").equalsIgnoreCase("intelligent")) return;
        //Pic.PIC_SetIRQMask(mpu.irq,true);
        irqDevice.setIRQ(mpu.irq, 0);
    }

    public static void MPU401_Init() {
        test = new MPU401();
        event = timeSource.newTimer(MPU401_Event);
        irqDevice.setIRQ(mpu.irq, 0);
        MPU401_Reset();
    }
}
