package common.network;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;

import common.collect.BiMap;
import common.collect.HashBiMap;
import common.collect.Maps;
import common.packet.CPacketPlayerPosition;
import common.packet.CPacketPlayerLook;
import common.packet.CPacketPlayerPosLook;
import common.packet.CPacketAction;
import common.packet.CPacketBreak;
import common.packet.CPacketCheat;
import common.packet.CPacketClick;
import common.packet.CPacketComplete;
import common.packet.CPacketForm;
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.LPacketChallenge;
import common.packet.LPacketPassword;
import common.packet.LPacketPubkey;
import common.packet.LPacketResponse;
import common.packet.LPacketStartEncrypt;
import common.packet.RPacketChallenge;
import common.packet.RPacketDisconnect;
import common.packet.RPacketEnableCompression;
import common.packet.RPacketLoginSuccess;
import common.packet.RPacketRequestEncrypt;
import common.packet.RPacketResponse;
import common.packet.RPacketServerConfig;
import common.packet.SPacketEntityRelMove;
import common.packet.SPacketEntityLook;
import common.packet.SPacketEntityLookMove;
import common.packet.SPacketEntity;
import common.packet.SPacketEntityTeleport;
import common.packet.SPacketEntityHeadLook;
import common.packet.SPacketEntityStatus;
import common.packet.SPacketEntityAttach;
import common.packet.SPacketEntityMetadata;
import common.packet.SPacketEntityEffect;
import common.packet.SPacketRemoveEntityEffect;
import common.packet.SPacketExplosion;
import common.packet.SPacketEffect;
import common.packet.SPacketSoundEffect;
import common.packet.SPacketParticles;
import common.packet.SPacketChangeGameState;
import common.packet.SPacketSpawnGlobalEntity;
import common.packet.SPacketOpenWindow;
import common.packet.SPacketCloseWindow;
import common.packet.SPacketSetSlot;
import common.packet.SPacketWindowItems;
import common.packet.SPacketWindowProperty;
import common.packet.SPacketConfirmTransaction;
import common.packet.SPacketUpdateSign;
import common.packet.SPacketUpdateTileEntity;
import common.packet.SPacketSignEditorOpen;
import common.packet.SPacketPlayerListItem;
import common.packet.SPacketPlayerAbilities;
import common.packet.SPacketTabComplete;
import common.packet.SPacketUpdateEntityTags;
import common.packet.SPacketAnimation;
import common.packet.SPacketBiome;
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.SPacketDisplayForm;
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(RPacketRequestEncrypt.class);
        this.server(RPacketResponse.class);
        this.server(RPacketChallenge.class);
        this.server(RPacketServerConfig.class);
        this.server(RPacketLoginSuccess.class);
        this.server(RPacketEnableCompression.class);
        
        this.client(LPacketStartEncrypt.class);
        this.client(LPacketPassword.class);
        this.client(LPacketChallenge.class);
        this.client(LPacketPubkey.class);
        this.client(LPacketResponse.class);
    }},
    PLAY {{
        this.server(SPacketKeepAlive.class);
        this.server(SPacketJoinGame.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(SPacketEntityVelocity.class);
        this.server(SPacketDestroyEntities.class);
        this.server(SPacketEntity.class);
        this.server(SPacketEntityRelMove.class);
        this.server(SPacketEntityLook.class);
        this.server(SPacketEntityLookMove.class);
        this.server(SPacketEntityTeleport.class);
        this.server(SPacketEntityHeadLook.class);
        this.server(SPacketEntityStatus.class);
        this.server(SPacketEntityAttach.class);
        this.server(SPacketEntityMetadata.class);
        this.server(SPacketEntityEffect.class);
        this.server(SPacketRemoveEntityEffect.class);
        this.server(SPacketSetExperience.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(SPacketExplosion.class);
        this.server(SPacketEffect.class);
        this.server(SPacketSoundEffect.class);
        this.server(SPacketParticles.class);
        this.server(SPacketChangeGameState.class);
        this.server(SPacketSpawnGlobalEntity.class);
        this.server(SPacketOpenWindow.class);
        this.server(SPacketCloseWindow.class);
        this.server(SPacketSetSlot.class);
        this.server(SPacketWindowItems.class);
        this.server(SPacketWindowProperty.class);
        this.server(SPacketConfirmTransaction.class);
        this.server(SPacketUpdateSign.class);
        this.server(SPacketUpdateTileEntity.class);
        this.server(SPacketSignEditorOpen.class);
        this.server(SPacketPlayerListItem.class);
        this.server(SPacketPlayerAbilities.class);
        this.server(SPacketTabComplete.class);
        this.server(SPacketSkin.class);
        this.server(SPacketDisconnect.class);
        this.server(SPacketWorld.class);
        this.server(SPacketCamera.class);
        this.server(SPacketBiome.class);
        this.server(SPacketUpdateEntityTags.class);
        this.server(SPacketTrades.class);
        this.server(SPacketDimensionName.class);
        this.server(SPacketCharacterList.class);
        this.server(SPacketServerTick.class);
        this.server(SPacketLoading.class);
        this.server(SPacketDisplayForm.class);
        
        this.client(CPacketKeepAlive.class);
        this.client(CPacketMessage.class);
        this.client(CPacketAction.class);
        this.client(CPacketPlayer.class);
        this.client(CPacketPlayerPosition.class);
        this.client(CPacketPlayerLook.class);
        this.client(CPacketPlayerPosLook.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(CPacketForm.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, NoSuchMethodException, InvocationTargetException {
        Class<? extends Packet> oclass = (client ? this.client : this.server).get(id);
        return oclass == null ? null : oclass.getConstructor().newInstance();
    }

    public static PacketRegistry getType(Packet packet) {
        return STATES.get(packet.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.getConstructor().newInstance();
					}
					catch(Throwable e) {
						throw new Error("Paket " + clazz + " kann nicht instanziert werden!");
					}
					STATES.put(clazz, reg);
				}
			}
		}
	}
}
