package net.minecraftforge.fml.common.network.handshake;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import net.minecraft.network.PacketBuffer;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.network.ByteBufUtils;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.network.internal.FMLProxyPacket;
import net.minecraftforge.fml.common.registry.GameData;

import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public abstract class FMLHandshakeMessage {
    public static FMLProxyPacket makeCustomChannelRegistration(Set<String> channels)
    {
        String salutation = Joiner.on('\0').join(Iterables.concat(Arrays.asList("FML|HS","FML", "FML|MP"),channels));
        FMLProxyPacket proxy = new FMLProxyPacket(new PacketBuffer(Unpooled.wrappedBuffer(salutation.getBytes(Charsets.UTF_8))), "REGISTER");
        return proxy;
    }
    public static class ServerHello extends FMLHandshakeMessage {
        private byte serverProtocolVersion;
        @Override
        public void toBytes(ByteBuf buffer)
        {
            buffer.writeByte(NetworkRegistry.FML_PROTOCOL);
        }

        @Override
        public void fromBytes(ByteBuf buffer)
        {
            serverProtocolVersion = buffer.readByte();
        }

        public byte protocolVersion()
        {
            return serverProtocolVersion;
        }
    }
    public static class ClientHello extends FMLHandshakeMessage {
        private byte serverProtocolVersion;
        @Override
        public void toBytes(ByteBuf buffer)
        {
            buffer.writeByte(NetworkRegistry.FML_PROTOCOL);
        }

        @Override
        public void fromBytes(ByteBuf buffer)
        {
            serverProtocolVersion = buffer.readByte();
        }

        public byte protocolVersion()
        {
            return serverProtocolVersion;
        }
    }
    public static class ModList extends FMLHandshakeMessage {
        public ModList()
        {

        }
        public ModList(List<ModContainer> modList)
        {
            for (ModContainer mod : modList)
            {
                modTags.put(mod.getModId(), mod.getVersion());
            }
        }
        private Map<String,String> modTags = Maps.newHashMap();

        @Override
        public void toBytes(ByteBuf buffer)
        {
            super.toBytes(buffer);
            ByteBufUtils.writeVarInt(buffer, modTags.size(), 2);
            for (Map.Entry<String,String> modTag: modTags.entrySet())
            {
                ByteBufUtils.writeUTF8String(buffer, modTag.getKey());
                ByteBufUtils.writeUTF8String(buffer, modTag.getValue());
            }
        }

        @Override
        public void fromBytes(ByteBuf buffer)
        {
            super.fromBytes(buffer);
            int modCount = ByteBufUtils.readVarInt(buffer, 2);
            for (int i = 0; i < modCount; i++)
            {
                modTags.put(ByteBufUtils.readUTF8String(buffer), ByteBufUtils.readUTF8String(buffer));
            }
        }

        public String modListAsString()
        {
            return Joiner.on(',').withKeyValueSeparator("@").join(modTags);
        }

        public int modListSize()
        {
            return modTags.size();
        }
        public Map<String, String> modList()
        {
            return modTags;
        }

        @Override
        public String toString(Class<? extends Enum<?>> side)
        {
            return super.toString(side)+":"+modTags.size()+" mods";
        }
    }

    public static class RegistryData extends FMLHandshakeMessage
    {
        public RegistryData()
        {

        }

        public RegistryData(boolean hasMore, String name, GameData.GameDataSnapshot.Entry entry)
        {
            this.hasMore = hasMore;
            this.name = name;
            this.ids = entry.ids;
            this.substitutions = entry.substitutions;
        }

        private boolean hasMore;
        private String name;
        private Map<String,Integer> ids;
        private Set<String> substitutions;

        @Override
        public void fromBytes(ByteBuf buffer)
        {
            this.hasMore = buffer.readBoolean();
            this.name = ByteBufUtils.readUTF8String(buffer);

            int length = ByteBufUtils.readVarInt(buffer, 3);
            ids = Maps.newHashMap();

            for (int i = 0; i < length; i++)
            {
                ids.put(ByteBufUtils.readUTF8String(buffer), ByteBufUtils.readVarInt(buffer, 3));
            }

            length = ByteBufUtils.readVarInt(buffer, 3);
            substitutions = Sets.newHashSet();

            for (int i = 0; i < length; i++)
            {
                substitutions.add(ByteBufUtils.readUTF8String(buffer));
            }
            //if (!buffer.isReadable()) return; // In case we expand
        }

        @Override
        public void toBytes(ByteBuf buffer)
        {
            buffer.writeBoolean(this.hasMore);
            ByteBufUtils.writeUTF8String(buffer, this.name);

            ByteBufUtils.writeVarInt(buffer, ids.size(), 3);
            for (Entry<String, Integer> entry: ids.entrySet())
            {
                ByteBufUtils.writeUTF8String(buffer, entry.getKey());
                ByteBufUtils.writeVarInt(buffer, entry.getValue(), 3);
            }

            ByteBufUtils.writeVarInt(buffer, substitutions.size(), 3);
            for (String entry: substitutions)
            {
                ByteBufUtils.writeUTF8String(buffer, entry);
            }
        }

        public Map<String,Integer> getIdMap()
        {
            return ids;
        }
        public Set<String> getSubstitutions()
        {
            return substitutions;
        }
        public String getName()
        {
            return this.name;
        }
        public boolean hasMore()
        {
            return this.hasMore;
        }

        @Override
        public String toString(Class<? extends Enum<?>> side)
        {
            return super.toString(side) + ":"+ids.size()+" mappings";
        }
    }
    public static class HandshakeAck extends FMLHandshakeMessage {
        int phase;
        public HandshakeAck() {}
        HandshakeAck(int phase)
        {
            this.phase = phase;
        }
        @Override
        public void fromBytes(ByteBuf buffer)
        {
            phase = buffer.readByte();
        }

        @Override
        public void toBytes(ByteBuf buffer)
        {
            buffer.writeByte(phase);
        }
        @Override
        public String toString(Class<? extends Enum<?>> side)
        {
            return super.toString(side) + ":{"+phase+"}";
        }
    }
    public static class HandshakeReset extends FMLHandshakeMessage {
        public HandshakeReset() {}
    }
    public void fromBytes(ByteBuf buffer)
    {
    }

    public void toBytes(ByteBuf buffer)
    {
    }

    public String toString(Class<? extends Enum<?>> side)
    {
        return getClass().getName().substring(getClass().getName().lastIndexOf('$'));
    }
}