package us.myles.ViaVersion.listeners;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import lombok.RequiredArgsConstructor;
import org.bukkit.block.Block;
import org.bukkit.block.CommandBlock;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.spacehq.opennbt.tag.builtin.ByteTag;
import org.spacehq.opennbt.tag.builtin.CompoundTag;
import us.myles.ViaVersion.ViaVersionPlugin;
import us.myles.ViaVersion.packets.PacketType;
import us.myles.ViaVersion.util.PacketUtil;
import us.myles.ViaVersion.util.ReflectionUtil;

import java.io.DataOutput;
import java.io.DataOutputStream;
import java.lang.reflect.Method;

@RequiredArgsConstructor
public class CommandBlockListener implements Listener {

    private final ViaVersionPlugin plugin;

    @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
    public void onJoin(final PlayerJoinEvent e) {
        if (e.getPlayer().isOp() && plugin.isPorted(e.getPlayer())) {
            ByteBuf buf = Unpooled.buffer();
            PacketUtil.writeVarInt(PacketType.PLAY_ENTITY_STATUS.getNewPacketID(), buf);
            buf.writeInt(e.getPlayer().getEntityId());
            buf.writeByte(26);
            plugin.sendRawPacket(e.getPlayer(), buf);
        }
    }

    @EventHandler(ignoreCancelled = true)
    public void onInteract(PlayerInteractEvent e) {
        if (e.getAction() == Action.RIGHT_CLICK_BLOCK && plugin.isPorted(e.getPlayer()) && e.getPlayer().isOp()) {
            try {
                sendCommandBlockPacket(e.getClickedBlock(), e.getPlayer());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    private void sendCommandBlockPacket(Block b, Player player) throws Exception {
        if (!(b.getState() instanceof CommandBlock))
            return;
        CommandBlock cmd = (CommandBlock) b.getState();

        Object tileEntityCommand = ReflectionUtil.get(cmd, "commandBlock", ReflectionUtil.nms("TileEntityCommand"));
        Object updatePacket = ReflectionUtil.invoke(tileEntityCommand, "getUpdatePacket");
        ByteBuf buf = packetToByteBuf(updatePacket);
        plugin.sendRawPacket(player, buf);
    }

    private ByteBuf packetToByteBuf(Object updatePacket) throws Exception {
        ByteBuf buf = Unpooled.buffer();
        PacketUtil.writeVarInt(PacketType.PLAY_UPDATE_BLOCK_ENTITY.getNewPacketID(), buf); //Packet ID
        long[] pos = getPosition(ReflectionUtil.get(updatePacket, "a", ReflectionUtil.nms("BlockPosition")));
        PacketUtil.writeBlockPosition(buf, pos[0], pos[1], pos[2]); //Block position
        buf.writeByte(2); //Action id always 2
        CompoundTag nbt = getNBT(ReflectionUtil.get(updatePacket, "c", ReflectionUtil.nms("NBTTagCompound")));
        if (nbt == null) {
            buf.writeByte(0); //If nbt is null. Use 0 as nbt
            return buf;
        }
        nbt.put(new ByteTag("powered", (byte) 0));
        nbt.put(new ByteTag("auto", (byte) 0));
        nbt.put(new ByteTag("conditionMet", (byte) 0));
        PacketUtil.writeNBT(buf, nbt); //NBT tag
        return buf;
    }

    private long[] getPosition(Object obj) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        return new long[]{
                (long) ReflectionUtil.getSuper(obj, "a", int.class), //X
                (long) ReflectionUtil.getSuper(obj, "c", int.class), //Y
                (long) ReflectionUtil.getSuper(obj, "d", int.class)  //Z
        };
    }

    private CompoundTag getNBT(Object obj) throws Exception {
        ByteBuf buf = Unpooled.buffer();
        Method m = ReflectionUtil.nms("NBTCompressedStreamTools").getMethod("a", ReflectionUtil.nms("NBTTagCompound"), DataOutput.class);
        m.invoke(null, obj, new DataOutputStream(new ByteBufOutputStream(buf)));
        try {
            return PacketUtil.readNBT(buf);
        } finally {
            buf.release();
        }
    }
}


@@ -181,16 +181,42 @@
 ructor;%0A
+import org.bukkit.Bukkit;%0A
 import o
@@ -234,24 +234,24 @@
 lock.Block;%0A
-
 import org.b
@@ -458,16 +458,72 @@
 Action;%0A
+import org.bukkit.event.player.PlayerChangedWorldEvent;%0A
 import o
@@ -610,24 +610,75 @@
 rJoinEvent;%0A
+import org.bukkit.event.player.PlayerRespawnEvent;%0A
 import org.s
@@ -1269,22 +1269,16 @@
  onJoin(
-final 
 PlayerJo
@@ -1294,27 +1294,30 @@
 ) %7B%0A        
-if 
+sendOp
 (e.getPlayer
@@ -1322,186 +1322,247 @@
 er()
-.isOp() && plugin.isPorted(e.getPlayer())) %7B%0A            ByteBuf buf = Unpooled.buffer();%0A            PacketUtil.writeVarInt(PacketType.PLAY_ENTITY_STATUS.getNewPacketID(), buf);
+);%0A    %7D%0A%0A    @EventHandler(priority = EventPriority.MONITOR)%0A    public void onRespawn(final PlayerRespawnEvent e) %7B%0A        Bukkit.getScheduler().runTaskLater(plugin, new Runnable() %7B%0A            @Override%0A            public void run() %7B
 %0A   
@@ -1574,20 +1574,18 @@
     
-buf.writeInt
+    sendOp
 (e.g
@@ -1598,22 +1598,8 @@
 er()
-.getEntityId()
 );%0A 
@@ -1613,90 +1613,174 @@
     
-buf.writeByte(26
+%7D%0A        %7D, 1L
 );%0A    
+%7D%0A%0A
     
-    plugin.sendRawPacket(e.getPlayer(), buf);%0A        %7D
+@EventHandler(priority = EventPriority.MONITOR)%0A    public void onWorldChange(PlayerChangedWorldEvent e) %7B%0A        sendOp(e.getPlayer());
 %0A   
@@ -2157,24 +2157,24 @@
 ackTrace();%0A
-
             
@@ -2184,32 +2184,381 @@
        %7D%0A    %7D%0A%0A
+    private void sendOp(Player p) %7B%0A        if (p.isOp() && plugin.isPorted(p)) %7B%0A            ByteBuf buf = Unpooled.buffer();%0A            PacketUtil.writeVarInt(PacketType.PLAY_ENTITY_STATUS.getNewPacketID(), buf);%0A            buf.writeInt(p.getEntityId());%0A            buf.writeByte(26);%0A            plugin.sendRawPacket(p, buf);%0A        %7D%0A    %7D%0A%0A
     private void

Fix unable to open commandblock after respawn/world change