package tld.testmod.client.midi;

import javax.annotation.Nullable;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Transmitter;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import tld.testmod.ModLogger;
import tld.testmod.network.PacketDispatcher;
import tld.testmod.network.server.ActiveReceiverMessage;

public enum MidiUtils implements Receiver
{

    INSTANCE;
    static BiMap<Integer, BlockPos> playerIdUsingBlock = HashBiMap.create();

    static IActiveNoteReceiver instrument;
    static World world;
    static BlockPos pos = null;
    static IBlockState state;
    static EntityPlayer player;
    static EnumHand hand;
    static EnumFacing facing;
    static float hitX, hitY, hitZ;
    static ItemStack stack = ItemStack.EMPTY;

    public void setNoteReceiver(IActiveNoteReceiver instrumentIn, World worldIn, BlockPos posIn, @Nullable IBlockState stateIn, EntityPlayer playerIn, EnumHand handIn, @Nullable EnumFacing facingIn,
            float hitXIn, float hitYIn, float hitZIn, ItemStack stackIn)
    {
        instrument = instrumentIn;
        world = worldIn;
        pos = posIn;
        state = stateIn;
        player = playerIn;
        hand = handIn;
        facing = facingIn;
        hitX = hitXIn;
        hitY = hitYIn;
        hitZ = hitZIn;
        stack = stackIn;

        if (worldIn.isRemote)
        {
            MidiDevice device;
            MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
            for (int i = 0; i < infos.length; i++)
            {
                try
                {
                    device = MidiSystem.getMidiDevice(infos[i]);
                    // does the device have any transmitters?
                    // if it does, add it to the device list
                    if (device.isOpen()) device.close();
                    ModLogger.info("%s, %d", infos[i], device.getMaxTransmitters());

                    if (device.getMaxTransmitters() != 0 && !(device instanceof Sequencer))
                    {
                        Transmitter trans = device.getTransmitter();
                        trans.setReceiver(getReceiver());

                        // open each device
                        device.open();
                        // if code gets this far without throwing an exception
                        // print a success message
                        ModLogger.info("%s was opened", device.getDeviceInfo());
                    }

                } catch (MidiUnavailableException e)
                {
                    ModLogger.error(e);
                }

            }
        }
    }

    public void setNoteReceiver(IActiveNoteReceiver instrumentIn, World worldIn, BlockPos posIn, @Nullable IBlockState stateIn, EntityPlayer playerIn, EnumHand handIn, @Nullable EnumFacing facingIn,
            float hitXIn, float hitYIn, float hitZIn)
    {
        setNoteReceiver(instrumentIn, worldIn, posIn, stateIn, playerIn, handIn, facingIn, hitXIn, hitYIn, hitZIn, ItemStack.EMPTY);
    }

    public void setNoteReceiver(IActiveNoteReceiver instrumentIn, World worldIn, EntityPlayer playerIn, EnumHand handIn, ItemStack stackIn)
    {
        setNoteReceiver(instrumentIn, worldIn, playerIn.getPosition(), null, playerIn, handIn, null, 0, 0, 0, stackIn);
    }

    private Receiver getReceiver()
    {
        return this;
    }

    @Override
    public void send(MidiMessage msg, long timeStamp)
    {
        byte[] message = msg.getMessage();
        if ((msg.getStatus() & 0xF0) == ShortMessage.NOTE_ON && pos != null)
        {
            // MIDI message [ (message & 0xF0 | channel & 0x0F), note, volume ]
            ActiveReceiverMessage packet = new ActiveReceiverMessage(pos, player.getEntityId(), hand, message[1], message[2]);
            PacketDispatcher.sendToServer(packet);
            ModLogger.info("  msg: %x, %x, %x, %d", msg.getStatus(), message[1], message[2], timeStamp);
        }
    }

    @Override
    public void close()
    {
        pos = null;
        stack = ItemStack.EMPTY;
    }

    public void notifyRemoved(World worldIn, BlockPos posIn)
    {
        if (pos != null && pos.equals(posIn))
        {
            ModLogger.info("ActiveNoteReceiver Removed: %s", posIn);
            pos = null;
            stack = ItemStack.EMPTY;
        }
    }

    public void notifyRemoved(World worldIn, ItemStack stackIn)
    {
        if (stackIn.equals(stack) && stack.getMetadata() == stackIn.getMetadata())
        {
            ModLogger.info("ActiveNoteReceiver Removed: %s", stackIn.getDisplayName());
            pos = null;
            stack = ItemStack.EMPTY;
        }
    }

}

@@ -4022,12 +4022,21 @@
    i
-f ((
+nt command = 
 msg.
@@ -4057,48 +4057,553 @@
 0xF0
-) == ShortMessage.NOTE_ON && pos != null
+;%0A        int channel = msg.getStatus() & 0x0F;%0A        boolean allChannels = true;%0A        boolean sendNoteOff = false;%0A        %0A        switch (command)%0A        %7B%0A        case ShortMessage.NOTE_OFF:%0A            message%5B2%5D = 0;%0A            break;%0A        case ShortMessage.NOTE_ON:%0A            break;%0A        default:%0A            return;%0A        %7D%0A%0A        boolean channelFlag = allChannels ? true : channel == 1;%0A        boolean noteOffFlag = sendNoteOff ? true : message%5B2%5D != 0;%0A        %0A        if (pos != null && channelFlag && noteOffFlag
 )%0A  
@@ -4624,16 +4624,35 @@
       //
+ NOTE_ON %7C NOTE_OFF
  MIDI me

Handle the two ways note off messages are generated. Add some initial logic for handling channels and whether to send note off messages to receivers.