package com.lastabyss.carbon.utils;

import gnu.trove.iterator.TIntObjectIterator;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import io.netty.buffer.Unpooled;

import java.io.IOException;

import com.lastabyss.carbon.utils.DataWatcherSerializer.DataWatcherObject.ValueType;

import net.minecraft.server.v1_8_R3.BlockPosition;
import net.minecraft.server.v1_8_R3.ItemStack;
import net.minecraft.server.v1_8_R3.PacketDataSerializer;
import net.minecraft.server.v1_8_R3.Vector3f;

public class DataWatcherSerializer {

	public static TIntObjectMap<DataWatcherObject> decodeData(byte[] data) throws IOException {
		TIntObjectMap<DataWatcherObject> map = new TIntObjectHashMap<>(10, 0.5f, -1);
		PacketDataSerializer serializer = new PacketDataSerializer(Unpooled.wrappedBuffer(data));
		do {
			final int b0 = serializer.readUnsignedByte();
			if (b0 == 127) {
				break;
			}
			final ValueType type = ValueType.fromId((b0 & 0xE0) >> 5);
			final int key = b0 & 0x1F;
			switch (type) {
				case BYTE: {
					map.put(key, new DataWatcherObject(type, serializer.readByte()));
					break;
				}
				case SHORT: {
					map.put(key, new DataWatcherObject(type, serializer.readShort()));
					break;
				}
				case INT: {
					map.put(key, new DataWatcherObject(type, serializer.readInt()));
					break;
				}
				case FLOAT: {
					map.put(key, new DataWatcherObject(type, serializer.readFloat()));
					break;
				}
				case STRING: {
					map.put(key, new DataWatcherObject(type, PacketDataSerializerHelper.readString(serializer, 32767)));
					break;
				}
				case ITEMSTACK: {
					map.put(key, new DataWatcherObject(type, PacketDataSerializerHelper.readItemStack(serializer)));
					break;
				}
				case VECTOR3I: {
					final int x = serializer.readInt();
					final int y = serializer.readInt();
					final int z = serializer.readInt();
					map.put(key, new DataWatcherObject(type, new BlockPosition(x, y, z)));
					break;
				}
				case VECTOR3F: {
					final float x = serializer.readFloat();
					final float y = serializer.readFloat();
					final float z = serializer.readFloat();
					map.put(key, new DataWatcherObject(type, new Vector3f(x, y, z)));
					break;
				}
			}
		} while (true);
		return map;
	}

	public static byte[] encodeData(TIntObjectMap<DataWatcherObject> objects) {
		PacketDataSerializer serializer = new PacketDataSerializer(Unpooled.buffer());
		TIntObjectIterator<DataWatcherObject> iterator = objects.iterator();
		while (iterator.hasNext()) {
			iterator.advance();
			DataWatcherObject object = iterator.value();
			final int tk = ((object.type.getId() << 5) | (iterator.key() & 0x1F)) & 0xFF;
			serializer.writeByte(tk);
			switch (object.type) {
				case BYTE: {
					serializer.writeByte((byte) object.value);
					break;
				}
				case SHORT: {
					serializer.writeShort((short) object.value);
					break;
				}
				case INT: {
					serializer.writeInt((int) object.value);
					break;
				}
				case FLOAT: {
					serializer.writeFloat((float) object.value);
					break;
				}
				case STRING: {
				    PacketDataSerializerHelper.writeString(serializer, (String) object.value);
					break;
				}
				case ITEMSTACK: {
				    PacketDataSerializerHelper.writeItemStack(serializer, (ItemStack) object.value);
					break;
				}
				case VECTOR3I: {
					BlockPosition blockPos = (BlockPosition) object.value;
					serializer.writeInt(blockPos.getX());
					serializer.writeInt(blockPos.getY());
					serializer.writeInt(blockPos.getZ());
					break;
				}
				case VECTOR3F: {
					Vector3f vector = (Vector3f) object.value;
					serializer.writeFloat(vector.getX());
					serializer.writeFloat(vector.getY());
					serializer.writeFloat(vector.getZ());
					break;
				}
			}
		}
		serializer.writeByte(127);
		return Utils.toArray(serializer);
	}

	public static class DataWatcherObject {

		public ValueType type;
		public Object value;

		public DataWatcherObject(ValueType type, Object value) {
			this.type = type;
			this.value = value;
		}

		public void toByte() {
			type = ValueType.BYTE;
			value = ((Number) value).byteValue();
		}

		public void toShort() {
			type = ValueType.SHORT;
			value = ((Number) value).shortValue();
		}

		public void toInt() {
			type = ValueType.INT;
			value = ((Number) value).intValue();
		}

		public void toFloat() {
			type = ValueType.FLOAT;
			value = ((Number) value).floatValue();
		}

		@Override
		public String toString() {
			return "type: " + type + " " + "value: " + value;
		}

		public enum ValueType {
			BYTE, SHORT, INT, FLOAT, STRING, ITEMSTACK, VECTOR3I, VECTOR3F;

			public int getId() {
				return ordinal();
			}

			public static ValueType fromId(int id) {
				return values()[id];
			}
		}

	}

}