package org.jpc.emulator.peripheral;

import org.jpc.j2se.Option;

import javax.sound.midi.*;
import java.io.InputStream;
import java.util.logging.*;

public class Midi
{
    private static final Logger Log = Logger.getLogger(Midi.class.getName());
    static final private int SYSEX_SIZE = 1024;
    static final private int RAWBUF	= 1024;

    static final private byte[] MIDI_evt_len = new byte[] {
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x00
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x10
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x20
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x30
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x40
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x50
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x60
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x70

      3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0x80
      3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0x90
      3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0xa0
      3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0xb0

      2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2,  // 0xc0
      2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2,  // 0xd0

      3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0xe0

      0,2,3,2, 0,0,1,0, 1,0,1,1, 1,0,1,0   // 0xf0
    };

    static private class _midi {
        int status;
        int cmd_len;
        int cmd_pos;
        byte[] cmd_buf = new byte[8];
        byte[] rt_buf = new byte[8];
        public static class Sysex {
            byte[] buf = new byte[SYSEX_SIZE];
            int used;
            int delay;
		    long start;
        }
        public Sysex sysex = new Sysex();
        Receiver handler;
        MidiDevice device;
    }
    static private _midi midi;
    static private ShortMessage msg = new ShortMessage();
    static private SysexMessage sysex_msg = new SysexMessage();

    static public void MIDI_RawOutByte(/*Bit8u*/int data)
    {
        if (midi.sysex.start!=0) {
            /*Bit32u*/long passed_ticks = System.currentTimeMillis() - midi.sysex.start;
            if (passed_ticks < midi.sysex.delay) try {Thread.sleep(midi.sysex.delay - passed_ticks);} catch (InterruptedException e){}
        }
        /* Test for a realtime MIDI message */
        if (data>=0xf8) {
            try {msg.setMessage(data);} catch (Exception e) {}
            midi.handler.send(msg, -1);
            return;
        }
        /* Test for a active sysex tranfer */
        if (midi.status==0xf0) {
            if ((data&0x80)==0) {
                if (midi.sysex.used<(SYSEX_SIZE-1)) midi.sysex.buf[midi.sysex.used++]=(byte)data;
                return;
            } else {
                midi.sysex.buf[midi.sysex.used++]=(byte)0xf7;

                if ((midi.sysex.start!=0) && (midi.sysex.used >= 4) && (midi.sysex.used <= 9) && (midi.sysex.buf[1] == 0x41) && (midi.sysex.buf[3] == 0x16)) {
                    Log.log(Level.WARNING,"MIDI:Skipping invalid MT-32 SysEx midi message (too short to contain a checksum)");
                } else {
    //				LOG(LOG_ALL,LOG_NORMAL)("Play sysex; address:%02X %02X %02X, length:%4d, delay:%3d", midi.sysex.buf[5], midi.sysex.buf[6], midi.sysex.buf[7], midi.sysex.used, midi.sysex.delay);
                    try {sysex_msg.setMessage(midi.sysex.buf, midi.sysex.used);} catch (Exception e) {}
                    midi.handler.send(sysex_msg, -1);
                    if (midi.sysex.start!=0) {
                        if (midi.sysex.buf[5] == 0x7F) {
                            midi.sysex.delay = 290; // All Parameters reset
                        } else if (midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x04) {
                            midi.sysex.delay = 145; // Viking Child
                        } else if (midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x01) {
                            midi.sysex.delay = 30; // Dark Sun 1
                        } else midi.sysex.delay = (/*Bitu*/int)(((float)(midi.sysex.used) * 1.25f) * 1000.0f / 3125.0f) + 2;
                        midi.sysex.start = System.currentTimeMillis();
                    }
                }

                Log.log(Level.INFO,"Sysex message size "+midi.sysex.used);
//                if (CaptureState & CAPTURE_MIDI) {
//                    CAPTURE_AddMidi( true, midi.sysex.used-1, &midi.sysex.buf[1]);
//                }
            }
        }
        if ((data&0x80)!=0) {
            midi.status=data;
            midi.cmd_pos=0;
            midi.cmd_len=MIDI_evt_len[data];
            if (midi.status==0xf0) {
                midi.sysex.buf[0]=(byte)0xf0;
                midi.sysex.used=1;
            }
        }
        if (midi.cmd_len!=0) {
            midi.cmd_buf[midi.cmd_pos++]=(byte)data;
            if (midi.cmd_pos >= midi.cmd_len) {
//                if (CaptureState & CAPTURE_MIDI) {
//                    CAPTURE_AddMidi(false, midi.cmd_len, midi.cmd_buf);
//                }
                try {msg.setMessage(midi.cmd_buf[0], midi.cmd_buf[1], midi.cmd_buf[2]);} catch (Exception e) {}
                midi.handler.send(msg, -1);
                midi.cmd_pos=1;		//Use Running status
            }
        }
    }

    static public boolean MIDI_Available()  {
        return midi.device != null;
    }
    
    public Midi() {
		String dev = Option.mididevice.value("default");
        String conf = Option.midiconfig.value("");
		/* If device = "default" go for first handler that works */
//		MAPPER_AddHandler(MIDI_SaveRawEvent,MK_f8,MMOD1|MMOD2,"caprawmidi","Cap MIDI");
        midi.sysex.delay = 0;
		midi.sysex.start = 0;
		if (conf.contains("delaysysex")) {
			midi.sysex.start = System.currentTimeMillis();
            conf = conf.replace("delaysysex", "").trim();
			Log.log(Level.INFO, "MIDI:Using delayed SysEx processing");
		}

		midi.status=0x00;
		midi.cmd_pos=0;
		midi.cmd_len=0;
        MidiDevice.Info[] devices = null;

        try {
            devices = MidiSystem.getMidiDeviceInfo();
        } catch (Exception e) {

        }
        boolean def = dev.equalsIgnoreCase("default");

        if (!def) {
            if (devices != null) {
                for (int i=0;i<devices.length;i++) {
                    if (devices[i].getName().equalsIgnoreCase(dev)) {
                        try {
                            MidiDevice device = MidiSystem.getMidiDevice(devices[0]);
                            device.open();
                            midi.handler =  device.getReceiver();
                            midi.device = device;
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            if (midi.handler==null) {
                Log.log(Level.INFO, "MIDI:Can't find device:"+dev+", finding default handler.");
            }
        }
        if (midi.handler == null) {
            Synthesizer	synth = null;
            Soundbank soundbank = null;

            try {
                synth=MidiSystem.getSynthesizer();
            } catch (Exception e) {
            }
            if (synth != null) {
                String fileName = "resources/soundbank-deluxe.gm";
                InputStream is = Midi.class.getResourceAsStream(fileName);
                if (is == null) {
                    fileName = "resources/soundbank-mid.gm";
                    is = Midi.class.getResourceAsStream(fileName);
                }
                if (is == null) {
                    fileName = "resources/soundbank-min.gm";
                    is = Midi.class.getResourceAsStream(fileName);
                }
                if (is != null) {
                    try {
                        soundbank = MidiSystem.getSoundbank(is);
                    } catch (Exception e) {
                    }
                    try {is.close();} catch (Exception e) {}
                }
                if (soundbank == null)
                {
                    fileName = "default";
                    try {
                        soundbank = synth.getDefaultSoundbank();
                    } catch (Exception e) {}
                }
                if (soundbank != null) {
                    try {
                        synth.open();
                        if (synth.isSoundbankSupported(soundbank) && synth.loadAllInstruments(soundbank)) {
                            Log.log(Level.INFO, "MIDI: Using Soundbank: "+fileName);
                            midi.handler = synth.getReceiver();
                            midi.device = synth;
                        } else {
                            synth.close();
                        }
                    } catch (Exception e) {
                    }
                }
            }
        }
        if (devices != null) {
            for (int i=0;i<devices.length && midi.handler == null;i++) {
                try {
                    MidiDevice device = MidiSystem.getMidiDevice(devices[i]);
                    device.open();
                    midi.handler =  device.getReceiver();
                    midi.device = device;
                    Log.log(Level.INFO, "MIDI:Opened device:"+devices[i].getName());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        if (midi.device == null) {
            Log.log(Level.INFO, "MIDI:Can't find any device");
        }
    }

    private static Midi test;

    public static void MIDI_Destroy() {
        if(midi.device!=null) {
            midi.handler.close();
            midi.device.close();
        }
        test = null;
        midi = null;
    }

    public static void MIDI_Init() {
        midi = new _midi();
        test = new Midi();
    }
}
