package common.network;

import java.util.Map;

import common.collect.BiMap;
import common.collect.HashBiMap;
import common.collect.Maps;
import common.packet.CPacketAction;
import common.packet.CPacketBook;
import common.packet.CPacketBreak;
import common.packet.CPacketCheat;
import common.packet.CPacketClick;
import common.packet.CPacketComplete;
import common.packet.CPacketInput;
import common.packet.CPacketKeepAlive;
import common.packet.CPacketMessage;
import common.packet.CPacketPlace;
import common.packet.CPacketPlayer;
import common.packet.CPacketSign;
import common.packet.CPacketSkin;
import common.packet.HPacketHandshake;
import common.packet.LPacketPasswordResponse;
import common.packet.RPacketDisconnect;
import common.packet.RPacketEnableCompression;
import common.packet.RPacketLoginSuccess;
import common.packet.S14PacketEntity;
import common.packet.S18PacketEntityTeleport;
import common.packet.S19PacketEntityHeadLook;
import common.packet.S1APacketEntityStatus;
import common.packet.S1BPacketEntityAttach;
import common.packet.S1CPacketEntityMetadata;
import common.packet.S1DPacketEntityEffect;
import common.packet.S1EPacketRemoveEntityEffect;
import common.packet.S20PacketEntityProperties;
import common.packet.S27PacketExplosion;
import common.packet.S28PacketEffect;
import common.packet.S29PacketSoundEffect;
import common.packet.S2APacketParticles;
import common.packet.S2BPacketChangeGameState;
import common.packet.S2CPacketSpawnGlobalEntity;
import common.packet.S2DPacketOpenWindow;
import common.packet.S2EPacketCloseWindow;
import common.packet.S2FPacketSetSlot;
import common.packet.S30PacketWindowItems;
import common.packet.S31PacketWindowProperty;
import common.packet.S32PacketConfirmTransaction;
import common.packet.S33PacketUpdateSign;
import common.packet.S35PacketUpdateTileEntity;
import common.packet.S36PacketSignEditorOpen;
import common.packet.S38PacketPlayerListItem;
import common.packet.S39PacketPlayerAbilities;
import common.packet.S3APacketTabComplete;
import common.packet.S43PacketUpdateEntityNBT;
import common.packet.SPacketAnimation;
import common.packet.SPacketBiomes;
import common.packet.SPacketBlockAction;
import common.packet.SPacketBlockBreakAnim;
import common.packet.SPacketBlockChange;
import common.packet.SPacketCamera;
import common.packet.SPacketCharacterList;
import common.packet.SPacketChunkData;
import common.packet.SPacketCollectItem;
import common.packet.SPacketDestroyEntities;
import common.packet.SPacketDimensionName;
import common.packet.SPacketDisconnect;
import common.packet.SPacketEntityEquipment;
import common.packet.SPacketEntityVelocity;
import common.packet.SPacketHeldItemChange;
import common.packet.SPacketJoinGame;
import common.packet.SPacketKeepAlive;
import common.packet.SPacketLoading;
import common.packet.SPacketMapChunkBulk;
import common.packet.SPacketMessage;
import common.packet.SPacketMultiBlockChange;
import common.packet.SPacketPlayerPosLook;
import common.packet.SPacketRespawn;
import common.packet.SPacketServerTick;
import common.packet.SPacketSetExperience;
import common.packet.SPacketSkin;
import common.packet.SPacketSpawnMob;
import common.packet.SPacketSpawnObject;
import common.packet.SPacketSpawnPlayer;
import common.packet.SPacketTimeUpdate;
import common.packet.SPacketTrades;
import common.packet.SPacketUpdateHealth;
import common.packet.SPacketWorld;

public enum PacketRegistry
{
    HANDSHAKE
    {
        {
            this.client(HPacketHandshake.class);
        }
    },
    LOGIN
    {
        {
            this.server(RPacketDisconnect.class);
            this.server(RPacketLoginSuccess.class);
            this.server(RPacketEnableCompression.class);
//            this.server(RPacketPasswordRequest.class);
//            this.client(LPacketLoginStart.class);
            this.client(LPacketPasswordResponse.class);
        }
    },
    PLAY
    {
        {
            this.server(SPacketKeepAlive.class);
            this.server(SPacketJoinGame.class);
            this.server(SPacketMessage.class);
//            this.server(SPacketMessage.class);
            this.server(SPacketTimeUpdate.class);
            this.server(SPacketEntityEquipment.class);
            this.server(SPacketUpdateHealth.class);
            this.server(SPacketRespawn.class);
            this.server(SPacketPlayerPosLook.class);
            this.server(SPacketHeldItemChange.class);
            this.server(SPacketAnimation.class);
            this.server(SPacketSpawnPlayer.class);
            this.server(SPacketCollectItem.class);
            this.server(SPacketSpawnObject.class);
            this.server(SPacketSpawnMob.class);
//            this.server(SPacketSpawnPainting.class);
//            this.server(SPacketSpawnExperienceOrb.class);
            this.server(SPacketEntityVelocity.class);
            this.server(SPacketDestroyEntities.class);
            this.server(S14PacketEntity.class);
            this.server(S14PacketEntity.S15PacketEntityRelMove.class);
            this.server(S14PacketEntity.S16PacketEntityLook.class);
            this.server(S14PacketEntity.S17PacketEntityLookMove.class);
            this.server(S18PacketEntityTeleport.class);
            this.server(S19PacketEntityHeadLook.class);
            this.server(S1APacketEntityStatus.class);
            this.server(S1BPacketEntityAttach.class);
            this.server(S1CPacketEntityMetadata.class);
            this.server(S1DPacketEntityEffect.class);
            this.server(S1EPacketRemoveEntityEffect.class);
            this.server(SPacketSetExperience.class);
            this.server(S20PacketEntityProperties.class);
            this.server(SPacketChunkData.class);
            this.server(SPacketMultiBlockChange.class);
            this.server(SPacketBlockChange.class);
            this.server(SPacketBlockAction.class);
            this.server(SPacketBlockBreakAnim.class);
            this.server(SPacketMapChunkBulk.class);
            this.server(S27PacketExplosion.class);
            this.server(S28PacketEffect.class);
            this.server(S29PacketSoundEffect.class);
            this.server(S2APacketParticles.class);
            this.server(S2BPacketChangeGameState.class);
            this.server(S2CPacketSpawnGlobalEntity.class);
            this.server(S2DPacketOpenWindow.class);
            this.server(S2EPacketCloseWindow.class);
            this.server(S2FPacketSetSlot.class);
            this.server(S30PacketWindowItems.class);
            this.server(S31PacketWindowProperty.class);
            this.server(S32PacketConfirmTransaction.class);
            this.server(S33PacketUpdateSign.class);
//            this.server(S34PacketMaps.class);
            this.server(S35PacketUpdateTileEntity.class);
            this.server(S36PacketSignEditorOpen.class);
//            this.server(S37PacketStatistics.class);
            this.server(S38PacketPlayerListItem.class);
            this.server(S39PacketPlayerAbilities.class);
            this.server(S3APacketTabComplete.class);
//            this.server(SPacketDisplay.class);
            this.server(SPacketSkin.class);
            this.server(SPacketDisconnect.class);
            this.server(SPacketWorld.class);
//            this.server(SPacketCapes.class);
            this.server(SPacketCamera.class);
            this.server(SPacketBiomes.class);
//            this.server(S42PacketTitle.class);
            this.server(S43PacketUpdateEntityNBT.class);
//            this.server(SPacketBook.class);
            this.server(SPacketTrades.class);
//            this.server(SPacketNotify.class);
            this.server(SPacketDimensionName.class);
            this.server(SPacketCharacterList.class);
            this.server(SPacketServerTick.class);
            this.server(SPacketLoading.class);
            
            this.client(CPacketKeepAlive.class);
            this.client(CPacketMessage.class);
            this.client(CPacketAction.class);
            this.client(CPacketPlayer.class);
            this.client(CPacketPlayer.C04PacketPlayerPosition.class);
            this.client(CPacketPlayer.C05PacketPlayerLook.class);
            this.client(CPacketPlayer.C06PacketPlayerPosLook.class);
            this.client(CPacketBreak.class);
            this.client(CPacketPlace.class);
            this.client(CPacketInput.class);
            this.client(CPacketClick.class);
            this.client(CPacketCheat.class);
            this.client(CPacketComplete.class);
            this.client(CPacketSkin.class);
            this.client(CPacketSign.class);
            this.client(CPacketBook.class);
//            this.client(CPacketCmdBlock.class);
        }
    };
	
    private static final Map<Class<? extends Packet>, PacketRegistry> STATES = Maps.<Class<? extends Packet>, PacketRegistry>newHashMap();
    
    private final BiMap<Integer, Class<? extends Packet>> server = HashBiMap.<Integer, Class<? extends Packet>>create();
    private final BiMap<Integer, Class<? extends Packet>> client = HashBiMap.<Integer, Class<? extends Packet>>create();

    protected void server(Class <? extends Packet > clazz)
    {
        if(this.server.containsValue(clazz))
            throw new IllegalArgumentException("S-Paket " + clazz + " ist bereits bekannt unter ID " + this.server.inverse().get(clazz));
        this.server.put(Integer.valueOf(this.server.size()), clazz);
    }

    protected void client(Class <? extends Packet > clazz)
    {
        if(this.client.containsValue(clazz))
            throw new IllegalArgumentException("C-Paket " + clazz + " ist bereits bekannt unter ID " + this.client.inverse().get(clazz));
        this.client.put(Integer.valueOf(this.client.size()), clazz);
    }

    public Integer getId(boolean client, Packet packet)
    {
        return (client ? this.client : this.server).inverse().get(packet.getClass());
    }

    public Packet getPacket(boolean client, int id) throws InstantiationException, IllegalAccessException {
        Class<? extends Packet> oclass = (client ? this.client : this.server).get(id);
        return oclass == null ? null : oclass.newInstance();
    }

    public static PacketRegistry getType(Packet packetIn)
    {
        return STATES.get(packetIn.getClass());
    }

	static {
		for(PacketRegistry reg : values()) {
			for(BiMap<Integer, Class<? extends Packet>> map : new BiMap[] {reg.server, reg.client}) {
				for(Class<? extends Packet> clazz : map.values()) {
					if(STATES.containsKey(clazz) && STATES.get(clazz) != reg) {
						throw new Error("Paket " + clazz + " ist bereits zu ID " + STATES.get(clazz)
								+ " zugewiesen - kann nicht auf " + reg + " neu zuweisen");
					}
					try {
						clazz.newInstance();
					}
					catch(Throwable e) {
						throw new Error("Paket " + clazz + " kann nicht instanziert werden!");
					}
					STATES.put(clazz, reg);
				}
			}
		}
	}
}
