package net.md_5.bungee.entitymap;

import io.netty.buffer.ByteBuf;
import java.util.UUID;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;

class EntityMap_1_8 extends EntityMap
{

    static final EntityMap_1_8 INSTANCE = new EntityMap_1_8();

    EntityMap_1_8()
    {
        addRewrite( 0x04, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Equipment
        addRewrite( 0x0A, ProtocolConstants.Direction.TO_CLIENT, true ); // Use bed
        addRewrite( 0x0B, ProtocolConstants.Direction.TO_CLIENT, true ); // Animation
        addRewrite( 0x0C, ProtocolConstants.Direction.TO_CLIENT, true ); // Spawn Player
        addRewrite( 0x0D, ProtocolConstants.Direction.TO_CLIENT, true ); // Collect Item
        addRewrite( 0x0E, ProtocolConstants.Direction.TO_CLIENT, true ); // Spawn Object
        addRewrite( 0x0F, ProtocolConstants.Direction.TO_CLIENT, true ); // Spawn Mob
        addRewrite( 0x10, ProtocolConstants.Direction.TO_CLIENT, true ); // Spawn Painting
        addRewrite( 0x11, ProtocolConstants.Direction.TO_CLIENT, true ); // Spawn Experience Orb
        addRewrite( 0x12, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Velocity
        addRewrite( 0x14, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity
        addRewrite( 0x15, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Relative Move
        addRewrite( 0x16, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Look
        addRewrite( 0x17, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Look and Relative Move
        addRewrite( 0x18, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Teleport
        addRewrite( 0x19, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Head Look
        addRewrite( 0x1A, ProtocolConstants.Direction.TO_CLIENT, false ); // Entity Status
        addRewrite( 0x1B, ProtocolConstants.Direction.TO_CLIENT, false ); // Attach Entity
        addRewrite( 0x1C, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Metadata
        addRewrite( 0x1D, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Effect
        addRewrite( 0x1E, ProtocolConstants.Direction.TO_CLIENT, true ); // Remove Entity Effect
        addRewrite( 0x20, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Properties
        addRewrite( 0x25, ProtocolConstants.Direction.TO_CLIENT, true ); // Block Break Animation
        addRewrite( 0x2C, ProtocolConstants.Direction.TO_CLIENT, true ); // Spawn Global Entity
        addRewrite( 0x43, ProtocolConstants.Direction.TO_CLIENT, true ); // Camera
        addRewrite( 0x49, ProtocolConstants.Direction.TO_CLIENT, true ); // Update Entity NBT

        addRewrite( 0x02, ProtocolConstants.Direction.TO_SERVER, true ); // Use Entity
        addRewrite( 0x0B, ProtocolConstants.Direction.TO_SERVER, true ); // Entity Action
    }

    @Override
    public void rewriteClientbound(ByteBuf packet, int oldId, int newId)
    {
        super.rewriteClientbound( packet, oldId, newId );

        //Special cases
        int readerIndex = packet.readerIndex();
        int packetId = DefinedPacket.readVarInt( packet );
        int packetIdLength = packet.readerIndex() - readerIndex;
        if ( packetId == 0x0D /* Collect Item */ )
        {
            DefinedPacket.readVarInt( packet );
            rewriteVarInt( packet, oldId, newId, packet.readerIndex() );
        } else if ( packetId == 0x1B /* Attach Entity */ )
        {
            rewriteInt( packet, oldId, newId, readerIndex + packetIdLength + 4 );
        } else if ( packetId == 0x13 /* Destroy Entities */ )
        {
            int count = DefinedPacket.readVarInt( packet );
            int[] ids = new int[ count ];
            for ( int i = 0; i < count; i++ )
            {
                ids[i] = DefinedPacket.readVarInt( packet );
            }
            packet.readerIndex( readerIndex + packetIdLength );
            packet.writerIndex( readerIndex + packetIdLength );
            DefinedPacket.writeVarInt( count, packet );
            for ( int id : ids )
            {
                if ( id == oldId )
                {
                    id = newId;
                } else if ( id == newId )
                {
                    id = oldId;
                }
                DefinedPacket.writeVarInt( id, packet );
            }
        } else if ( packetId == 0x0E /* Spawn Object */ )
        {

            DefinedPacket.readVarInt( packet );
            int type = packet.readUnsignedByte();

            if ( type == 60 || type == 90 )
            {
                packet.skipBytes( 14 );
                int position = packet.readerIndex();
                int readId = packet.readInt();
                int changedId = readId;

                if ( readId == oldId )
                {
                    packet.setInt( position, changedId = newId );
                } else if ( readId == newId )
                {
                    packet.setInt( position, changedId = oldId );
                }

                if ( readId > 0 && changedId <= 0 )
                {
                    packet.writerIndex( packet.writerIndex() - 6 );
                } else if ( changedId > 0 && readId <= 0 )
                {
                    packet.ensureWritable( 6 );
                    packet.writerIndex( packet.writerIndex() + 6 );
                }
            }
        } else if ( packetId == 0x0C /* Spawn Player */ )
        {
            DefinedPacket.readVarInt( packet ); // Entity ID
            int idLength = packet.readerIndex() - readerIndex - packetIdLength;
            UUID uuid = DefinedPacket.readUUID( packet );
            UserConnection player;
            if ( ( player = BungeeCord.getInstance().getPlayerByOfflineUUID( uuid ) ) != null )
            {
                int previous = packet.writerIndex();
                packet.readerIndex( readerIndex );
                packet.writerIndex( readerIndex + packetIdLength + idLength );
                DefinedPacket.writeUUID( player.getRewriteId(), packet );
                packet.writerIndex( previous );
            }
        } else if ( packetId == 0x42 /* Combat Event */ )
        {
            int event = packet.readUnsignedByte();
            if ( event == 1 /* End Combat*/ )
            {
                DefinedPacket.readVarInt( packet );
                rewriteInt( packet, oldId, newId, packet.readerIndex() );
            } else if ( event == 2 /* Entity Dead */ )
            {
                int position = packet.readerIndex();
                rewriteVarInt( packet, oldId, newId, packet.readerIndex() );
                packet.readerIndex( position );
                DefinedPacket.readVarInt( packet );
                rewriteInt( packet, oldId, newId, packet.readerIndex() );
            }
        }
        packet.readerIndex( readerIndex );
    }

    @Override
    public void rewriteServerbound(ByteBuf packet, int oldId, int newId)
    {
        super.rewriteServerbound( packet, oldId, newId );
        //Special cases
        int readerIndex = packet.readerIndex();
        int packetId = DefinedPacket.readVarInt( packet );
        int packetIdLength = packet.readerIndex() - readerIndex;

        if ( packetId == 0x18 /* Spectate */ )
        {
            UUID uuid = DefinedPacket.readUUID( packet );
            ProxiedPlayer player;
            if ( ( player = BungeeCord.getInstance().getPlayer( uuid ) ) != null )
            {
                int previous = packet.writerIndex();
                packet.readerIndex( readerIndex );
                packet.writerIndex( readerIndex + packetIdLength );
                DefinedPacket.writeUUID( ( (UserConnection) player ).getRewriteId(), packet );
                packet.writerIndex( previous );
            }
        }
        packet.readerIndex( readerIndex );
    }
}
