// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.protocol;

import java.util.Objects;
import java.util.Arrays;
import com.hypixel.hytale.protocol.io.ValidationResult;
import java.util.Iterator;
import java.util.HashMap;
import com.hypixel.hytale.protocol.io.ProtocolException;
import com.hypixel.hytale.protocol.io.VarInt;
import com.hypixel.hytale.protocol.io.PacketIO;
import io.netty.buffer.ByteBuf;
import javax.annotation.Nonnull;
import java.util.Map;
import javax.annotation.Nullable;

public class Model
{
    public static final int NULLABLE_BIT_FIELD_SIZE = 2;
    public static final int FIXED_BLOCK_SIZE = 43;
    public static final int VARIABLE_FIELD_COUNT = 12;
    public static final int VARIABLE_BLOCK_START = 91;
    public static final int MAX_SIZE = 1677721600;
    @Nullable
    public String assetId;
    @Nullable
    public String path;
    @Nullable
    public String texture;
    @Nullable
    public String gradientSet;
    @Nullable
    public String gradientId;
    @Nullable
    public CameraSettings camera;
    public float scale;
    public float eyeHeight;
    public float crouchOffset;
    @Nullable
    public Map<String, AnimationSet> animationSets;
    @Nullable
    public ModelAttachment[] attachments;
    @Nullable
    public Hitbox hitbox;
    @Nullable
    public ModelParticle[] particles;
    @Nullable
    public ModelTrail[] trails;
    @Nullable
    public ColorLight light;
    @Nullable
    public Map<String, DetailBox[]> detailBoxes;
    @Nonnull
    public Phobia phobia;
    @Nullable
    public Model phobiaModel;
    
    public Model() {
        this.phobia = Phobia.None;
    }
    
    public Model(@Nullable final String assetId, @Nullable final String path, @Nullable final String texture, @Nullable final String gradientSet, @Nullable final String gradientId, @Nullable final CameraSettings camera, final float scale, final float eyeHeight, final float crouchOffset, @Nullable final Map<String, AnimationSet> animationSets, @Nullable final ModelAttachment[] attachments, @Nullable final Hitbox hitbox, @Nullable final ModelParticle[] particles, @Nullable final ModelTrail[] trails, @Nullable final ColorLight light, @Nullable final Map<String, DetailBox[]> detailBoxes, @Nonnull final Phobia phobia, @Nullable final Model phobiaModel) {
        this.phobia = Phobia.None;
        this.assetId = assetId;
        this.path = path;
        this.texture = texture;
        this.gradientSet = gradientSet;
        this.gradientId = gradientId;
        this.camera = camera;
        this.scale = scale;
        this.eyeHeight = eyeHeight;
        this.crouchOffset = crouchOffset;
        this.animationSets = animationSets;
        this.attachments = attachments;
        this.hitbox = hitbox;
        this.particles = particles;
        this.trails = trails;
        this.light = light;
        this.detailBoxes = detailBoxes;
        this.phobia = phobia;
        this.phobiaModel = phobiaModel;
    }
    
    public Model(@Nonnull final Model other) {
        this.phobia = Phobia.None;
        this.assetId = other.assetId;
        this.path = other.path;
        this.texture = other.texture;
        this.gradientSet = other.gradientSet;
        this.gradientId = other.gradientId;
        this.camera = other.camera;
        this.scale = other.scale;
        this.eyeHeight = other.eyeHeight;
        this.crouchOffset = other.crouchOffset;
        this.animationSets = other.animationSets;
        this.attachments = other.attachments;
        this.hitbox = other.hitbox;
        this.particles = other.particles;
        this.trails = other.trails;
        this.light = other.light;
        this.detailBoxes = other.detailBoxes;
        this.phobia = other.phobia;
        this.phobiaModel = other.phobiaModel;
    }
    
    @Nonnull
    public static Model deserialize(@Nonnull final ByteBuf buf, final int offset) {
        final Model obj = new Model();
        final byte[] nullBits = PacketIO.readBytes(buf, offset, 2);
        obj.scale = buf.getFloatLE(offset + 2);
        obj.eyeHeight = buf.getFloatLE(offset + 6);
        obj.crouchOffset = buf.getFloatLE(offset + 10);
        if ((nullBits[0] & 0x1) != 0x0) {
            obj.hitbox = Hitbox.deserialize(buf, offset + 14);
        }
        if ((nullBits[0] & 0x2) != 0x0) {
            obj.light = ColorLight.deserialize(buf, offset + 38);
        }
        obj.phobia = Phobia.fromValue(buf.getByte(offset + 42));
        if ((nullBits[0] & 0x4) != 0x0) {
            final int varPos0 = offset + 91 + buf.getIntLE(offset + 43);
            final int assetIdLen = VarInt.peek(buf, varPos0);
            if (assetIdLen < 0) {
                throw ProtocolException.negativeLength("AssetId", assetIdLen);
            }
            if (assetIdLen > 4096000) {
                throw ProtocolException.stringTooLong("AssetId", assetIdLen, 4096000);
            }
            obj.assetId = PacketIO.readVarString(buf, varPos0, PacketIO.UTF8);
        }
        if ((nullBits[0] & 0x8) != 0x0) {
            final int varPos2 = offset + 91 + buf.getIntLE(offset + 47);
            final int pathLen = VarInt.peek(buf, varPos2);
            if (pathLen < 0) {
                throw ProtocolException.negativeLength("Path", pathLen);
            }
            if (pathLen > 4096000) {
                throw ProtocolException.stringTooLong("Path", pathLen, 4096000);
            }
            obj.path = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8);
        }
        if ((nullBits[0] & 0x10) != 0x0) {
            final int varPos3 = offset + 91 + buf.getIntLE(offset + 51);
            final int textureLen = VarInt.peek(buf, varPos3);
            if (textureLen < 0) {
                throw ProtocolException.negativeLength("Texture", textureLen);
            }
            if (textureLen > 4096000) {
                throw ProtocolException.stringTooLong("Texture", textureLen, 4096000);
            }
            obj.texture = PacketIO.readVarString(buf, varPos3, PacketIO.UTF8);
        }
        if ((nullBits[0] & 0x20) != 0x0) {
            final int varPos4 = offset + 91 + buf.getIntLE(offset + 55);
            final int gradientSetLen = VarInt.peek(buf, varPos4);
            if (gradientSetLen < 0) {
                throw ProtocolException.negativeLength("GradientSet", gradientSetLen);
            }
            if (gradientSetLen > 4096000) {
                throw ProtocolException.stringTooLong("GradientSet", gradientSetLen, 4096000);
            }
            obj.gradientSet = PacketIO.readVarString(buf, varPos4, PacketIO.UTF8);
        }
        if ((nullBits[0] & 0x40) != 0x0) {
            final int varPos5 = offset + 91 + buf.getIntLE(offset + 59);
            final int gradientIdLen = VarInt.peek(buf, varPos5);
            if (gradientIdLen < 0) {
                throw ProtocolException.negativeLength("GradientId", gradientIdLen);
            }
            if (gradientIdLen > 4096000) {
                throw ProtocolException.stringTooLong("GradientId", gradientIdLen, 4096000);
            }
            obj.gradientId = PacketIO.readVarString(buf, varPos5, PacketIO.UTF8);
        }
        if ((nullBits[0] & 0x80) != 0x0) {
            final int varPos6 = offset + 91 + buf.getIntLE(offset + 63);
            obj.camera = CameraSettings.deserialize(buf, varPos6);
        }
        if ((nullBits[1] & 0x1) != 0x0) {
            final int varPos7 = offset + 91 + buf.getIntLE(offset + 67);
            final int animationSetsCount = VarInt.peek(buf, varPos7);
            if (animationSetsCount < 0) {
                throw ProtocolException.negativeLength("AnimationSets", animationSetsCount);
            }
            if (animationSetsCount > 4096000) {
                throw ProtocolException.dictionaryTooLarge("AnimationSets", animationSetsCount, 4096000);
            }
            final int varIntLen = VarInt.length(buf, varPos7);
            obj.animationSets = new HashMap<String, AnimationSet>(animationSetsCount);
            int dictPos = varPos7 + varIntLen;
            for (int i = 0; i < animationSetsCount; ++i) {
                final int keyLen = VarInt.peek(buf, dictPos);
                if (keyLen < 0) {
                    throw ProtocolException.negativeLength("key", keyLen);
                }
                if (keyLen > 4096000) {
                    throw ProtocolException.stringTooLong("key", keyLen, 4096000);
                }
                final int keyVarLen = VarInt.length(buf, dictPos);
                final String key = PacketIO.readVarString(buf, dictPos);
                dictPos += keyVarLen + keyLen;
                final AnimationSet val = AnimationSet.deserialize(buf, dictPos);
                dictPos += AnimationSet.computeBytesConsumed(buf, dictPos);
                if (obj.animationSets.put(key, val) != null) {
                    throw ProtocolException.duplicateKey("animationSets", key);
                }
            }
        }
        if ((nullBits[1] & 0x2) != 0x0) {
            final int varPos8 = offset + 91 + buf.getIntLE(offset + 71);
            final int attachmentsCount = VarInt.peek(buf, varPos8);
            if (attachmentsCount < 0) {
                throw ProtocolException.negativeLength("Attachments", attachmentsCount);
            }
            if (attachmentsCount > 4096000) {
                throw ProtocolException.arrayTooLong("Attachments", attachmentsCount, 4096000);
            }
            final int varIntLen = VarInt.length(buf, varPos8);
            if (varPos8 + varIntLen + attachmentsCount * 1L > buf.readableBytes()) {
                throw ProtocolException.bufferTooSmall("Attachments", varPos8 + varIntLen + attachmentsCount * 1, buf.readableBytes());
            }
            obj.attachments = new ModelAttachment[attachmentsCount];
            int elemPos = varPos8 + varIntLen;
            for (int i = 0; i < attachmentsCount; ++i) {
                obj.attachments[i] = ModelAttachment.deserialize(buf, elemPos);
                elemPos += ModelAttachment.computeBytesConsumed(buf, elemPos);
            }
        }
        if ((nullBits[1] & 0x4) != 0x0) {
            final int varPos9 = offset + 91 + buf.getIntLE(offset + 75);
            final int particlesCount = VarInt.peek(buf, varPos9);
            if (particlesCount < 0) {
                throw ProtocolException.negativeLength("Particles", particlesCount);
            }
            if (particlesCount > 4096000) {
                throw ProtocolException.arrayTooLong("Particles", particlesCount, 4096000);
            }
            final int varIntLen = VarInt.length(buf, varPos9);
            if (varPos9 + varIntLen + particlesCount * 34L > buf.readableBytes()) {
                throw ProtocolException.bufferTooSmall("Particles", varPos9 + varIntLen + particlesCount * 34, buf.readableBytes());
            }
            obj.particles = new ModelParticle[particlesCount];
            int elemPos = varPos9 + varIntLen;
            for (int i = 0; i < particlesCount; ++i) {
                obj.particles[i] = ModelParticle.deserialize(buf, elemPos);
                elemPos += ModelParticle.computeBytesConsumed(buf, elemPos);
            }
        }
        if ((nullBits[1] & 0x8) != 0x0) {
            final int varPos10 = offset + 91 + buf.getIntLE(offset + 79);
            final int trailsCount = VarInt.peek(buf, varPos10);
            if (trailsCount < 0) {
                throw ProtocolException.negativeLength("Trails", trailsCount);
            }
            if (trailsCount > 4096000) {
                throw ProtocolException.arrayTooLong("Trails", trailsCount, 4096000);
            }
            final int varIntLen = VarInt.length(buf, varPos10);
            if (varPos10 + varIntLen + trailsCount * 27L > buf.readableBytes()) {
                throw ProtocolException.bufferTooSmall("Trails", varPos10 + varIntLen + trailsCount * 27, buf.readableBytes());
            }
            obj.trails = new ModelTrail[trailsCount];
            int elemPos = varPos10 + varIntLen;
            for (int i = 0; i < trailsCount; ++i) {
                obj.trails[i] = ModelTrail.deserialize(buf, elemPos);
                elemPos += ModelTrail.computeBytesConsumed(buf, elemPos);
            }
        }
        if ((nullBits[1] & 0x10) != 0x0) {
            final int varPos11 = offset + 91 + buf.getIntLE(offset + 83);
            final int detailBoxesCount = VarInt.peek(buf, varPos11);
            if (detailBoxesCount < 0) {
                throw ProtocolException.negativeLength("DetailBoxes", detailBoxesCount);
            }
            if (detailBoxesCount > 4096000) {
                throw ProtocolException.dictionaryTooLarge("DetailBoxes", detailBoxesCount, 4096000);
            }
            final int varIntLen = VarInt.length(buf, varPos11);
            obj.detailBoxes = new HashMap<String, DetailBox[]>(detailBoxesCount);
            int dictPos = varPos11 + varIntLen;
            for (int i = 0; i < detailBoxesCount; ++i) {
                final int keyLen = VarInt.peek(buf, dictPos);
                if (keyLen < 0) {
                    throw ProtocolException.negativeLength("key", keyLen);
                }
                if (keyLen > 4096000) {
                    throw ProtocolException.stringTooLong("key", keyLen, 4096000);
                }
                final int keyVarLen = VarInt.length(buf, dictPos);
                final String key = PacketIO.readVarString(buf, dictPos);
                dictPos += keyVarLen + keyLen;
                final int valLen = VarInt.peek(buf, dictPos);
                if (valLen < 0) {
                    throw ProtocolException.negativeLength("val", valLen);
                }
                if (valLen > 64) {
                    throw ProtocolException.arrayTooLong("val", valLen, 64);
                }
                final int valVarLen = VarInt.length(buf, dictPos);
                if (dictPos + valVarLen + valLen * 37L > buf.readableBytes()) {
                    throw ProtocolException.bufferTooSmall("val", dictPos + valVarLen + valLen * 37, buf.readableBytes());
                }
                dictPos += valVarLen;
                final DetailBox[] val2 = new DetailBox[valLen];
                for (int valIdx = 0; valIdx < valLen; ++valIdx) {
                    val2[valIdx] = DetailBox.deserialize(buf, dictPos);
                    dictPos += DetailBox.computeBytesConsumed(buf, dictPos);
                }
                if (obj.detailBoxes.put(key, val2) != null) {
                    throw ProtocolException.duplicateKey("detailBoxes", key);
                }
            }
        }
        if ((nullBits[1] & 0x20) != 0x0) {
            final int varPos12 = offset + 91 + buf.getIntLE(offset + 87);
            obj.phobiaModel = deserialize(buf, varPos12);
        }
        return obj;
    }
    
    public static int computeBytesConsumed(@Nonnull final ByteBuf buf, final int offset) {
        final byte[] nullBits = PacketIO.readBytes(buf, offset, 2);
        int maxEnd = 91;
        if ((nullBits[0] & 0x4) != 0x0) {
            final int fieldOffset0 = buf.getIntLE(offset + 43);
            int pos0 = offset + 91 + fieldOffset0;
            final int sl = VarInt.peek(buf, pos0);
            pos0 += VarInt.length(buf, pos0) + sl;
            if (pos0 - offset > maxEnd) {
                maxEnd = pos0 - offset;
            }
        }
        if ((nullBits[0] & 0x8) != 0x0) {
            final int fieldOffset2 = buf.getIntLE(offset + 47);
            int pos2 = offset + 91 + fieldOffset2;
            final int sl = VarInt.peek(buf, pos2);
            pos2 += VarInt.length(buf, pos2) + sl;
            if (pos2 - offset > maxEnd) {
                maxEnd = pos2 - offset;
            }
        }
        if ((nullBits[0] & 0x10) != 0x0) {
            final int fieldOffset3 = buf.getIntLE(offset + 51);
            int pos3 = offset + 91 + fieldOffset3;
            final int sl = VarInt.peek(buf, pos3);
            pos3 += VarInt.length(buf, pos3) + sl;
            if (pos3 - offset > maxEnd) {
                maxEnd = pos3 - offset;
            }
        }
        if ((nullBits[0] & 0x20) != 0x0) {
            final int fieldOffset4 = buf.getIntLE(offset + 55);
            int pos4 = offset + 91 + fieldOffset4;
            final int sl = VarInt.peek(buf, pos4);
            pos4 += VarInt.length(buf, pos4) + sl;
            if (pos4 - offset > maxEnd) {
                maxEnd = pos4 - offset;
            }
        }
        if ((nullBits[0] & 0x40) != 0x0) {
            final int fieldOffset5 = buf.getIntLE(offset + 59);
            int pos5 = offset + 91 + fieldOffset5;
            final int sl = VarInt.peek(buf, pos5);
            pos5 += VarInt.length(buf, pos5) + sl;
            if (pos5 - offset > maxEnd) {
                maxEnd = pos5 - offset;
            }
        }
        if ((nullBits[0] & 0x80) != 0x0) {
            final int fieldOffset6 = buf.getIntLE(offset + 63);
            int pos6 = offset + 91 + fieldOffset6;
            pos6 += CameraSettings.computeBytesConsumed(buf, pos6);
            if (pos6 - offset > maxEnd) {
                maxEnd = pos6 - offset;
            }
        }
        if ((nullBits[1] & 0x1) != 0x0) {
            final int fieldOffset7 = buf.getIntLE(offset + 67);
            int pos7 = offset + 91 + fieldOffset7;
            final int dictLen = VarInt.peek(buf, pos7);
            pos7 += VarInt.length(buf, pos7);
            for (int i = 0; i < dictLen; ++i) {
                final int sl2 = VarInt.peek(buf, pos7);
                pos7 += VarInt.length(buf, pos7) + sl2;
                pos7 += AnimationSet.computeBytesConsumed(buf, pos7);
            }
            if (pos7 - offset > maxEnd) {
                maxEnd = pos7 - offset;
            }
        }
        if ((nullBits[1] & 0x2) != 0x0) {
            final int fieldOffset8 = buf.getIntLE(offset + 71);
            int pos8 = offset + 91 + fieldOffset8;
            final int arrLen = VarInt.peek(buf, pos8);
            pos8 += VarInt.length(buf, pos8);
            for (int i = 0; i < arrLen; ++i) {
                pos8 += ModelAttachment.computeBytesConsumed(buf, pos8);
            }
            if (pos8 - offset > maxEnd) {
                maxEnd = pos8 - offset;
            }
        }
        if ((nullBits[1] & 0x4) != 0x0) {
            final int fieldOffset9 = buf.getIntLE(offset + 75);
            int pos9 = offset + 91 + fieldOffset9;
            final int arrLen = VarInt.peek(buf, pos9);
            pos9 += VarInt.length(buf, pos9);
            for (int i = 0; i < arrLen; ++i) {
                pos9 += ModelParticle.computeBytesConsumed(buf, pos9);
            }
            if (pos9 - offset > maxEnd) {
                maxEnd = pos9 - offset;
            }
        }
        if ((nullBits[1] & 0x8) != 0x0) {
            final int fieldOffset10 = buf.getIntLE(offset + 79);
            int pos10 = offset + 91 + fieldOffset10;
            final int arrLen = VarInt.peek(buf, pos10);
            pos10 += VarInt.length(buf, pos10);
            for (int i = 0; i < arrLen; ++i) {
                pos10 += ModelTrail.computeBytesConsumed(buf, pos10);
            }
            if (pos10 - offset > maxEnd) {
                maxEnd = pos10 - offset;
            }
        }
        if ((nullBits[1] & 0x10) != 0x0) {
            final int fieldOffset11 = buf.getIntLE(offset + 83);
            int pos11 = offset + 91 + fieldOffset11;
            final int dictLen = VarInt.peek(buf, pos11);
            pos11 += VarInt.length(buf, pos11);
            for (int i = 0; i < dictLen; ++i) {
                final int sl2 = VarInt.peek(buf, pos11);
                pos11 += VarInt.length(buf, pos11) + sl2;
                final int al = VarInt.peek(buf, pos11);
                pos11 += VarInt.length(buf, pos11);
                for (int j = 0; j < al; ++j) {
                    pos11 += DetailBox.computeBytesConsumed(buf, pos11);
                }
            }
            if (pos11 - offset > maxEnd) {
                maxEnd = pos11 - offset;
            }
        }
        if ((nullBits[1] & 0x20) != 0x0) {
            final int fieldOffset12 = buf.getIntLE(offset + 87);
            int pos12 = offset + 91 + fieldOffset12;
            pos12 += computeBytesConsumed(buf, pos12);
            if (pos12 - offset > maxEnd) {
                maxEnd = pos12 - offset;
            }
        }
        return maxEnd;
    }
    
    public void serialize(@Nonnull final ByteBuf buf) {
        final int startPos = buf.writerIndex();
        final byte[] nullBits = new byte[2];
        if (this.hitbox != null) {
            final byte[] array = nullBits;
            final int n = 0;
            array[n] |= 0x1;
        }
        if (this.light != null) {
            final byte[] array2 = nullBits;
            final int n2 = 0;
            array2[n2] |= 0x2;
        }
        if (this.assetId != null) {
            final byte[] array3 = nullBits;
            final int n3 = 0;
            array3[n3] |= 0x4;
        }
        if (this.path != null) {
            final byte[] array4 = nullBits;
            final int n4 = 0;
            array4[n4] |= 0x8;
        }
        if (this.texture != null) {
            final byte[] array5 = nullBits;
            final int n5 = 0;
            array5[n5] |= 0x10;
        }
        if (this.gradientSet != null) {
            final byte[] array6 = nullBits;
            final int n6 = 0;
            array6[n6] |= 0x20;
        }
        if (this.gradientId != null) {
            final byte[] array7 = nullBits;
            final int n7 = 0;
            array7[n7] |= 0x40;
        }
        if (this.camera != null) {
            final byte[] array8 = nullBits;
            final int n8 = 0;
            array8[n8] |= (byte)128;
        }
        if (this.animationSets != null) {
            final byte[] array9 = nullBits;
            final int n9 = 1;
            array9[n9] |= 0x1;
        }
        if (this.attachments != null) {
            final byte[] array10 = nullBits;
            final int n10 = 1;
            array10[n10] |= 0x2;
        }
        if (this.particles != null) {
            final byte[] array11 = nullBits;
            final int n11 = 1;
            array11[n11] |= 0x4;
        }
        if (this.trails != null) {
            final byte[] array12 = nullBits;
            final int n12 = 1;
            array12[n12] |= 0x8;
        }
        if (this.detailBoxes != null) {
            final byte[] array13 = nullBits;
            final int n13 = 1;
            array13[n13] |= 0x10;
        }
        if (this.phobiaModel != null) {
            final byte[] array14 = nullBits;
            final int n14 = 1;
            array14[n14] |= 0x20;
        }
        buf.writeBytes(nullBits);
        buf.writeFloatLE(this.scale);
        buf.writeFloatLE(this.eyeHeight);
        buf.writeFloatLE(this.crouchOffset);
        if (this.hitbox != null) {
            this.hitbox.serialize(buf);
        }
        else {
            buf.writeZero(24);
        }
        if (this.light != null) {
            this.light.serialize(buf);
        }
        else {
            buf.writeZero(4);
        }
        buf.writeByte(this.phobia.getValue());
        final int assetIdOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int pathOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int textureOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int gradientSetOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int gradientIdOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int cameraOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int animationSetsOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int attachmentsOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int particlesOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int trailsOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int detailBoxesOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int phobiaModelOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int varBlockStart = buf.writerIndex();
        if (this.assetId != null) {
            buf.setIntLE(assetIdOffsetSlot, buf.writerIndex() - varBlockStart);
            PacketIO.writeVarString(buf, this.assetId, 4096000);
        }
        else {
            buf.setIntLE(assetIdOffsetSlot, -1);
        }
        if (this.path != null) {
            buf.setIntLE(pathOffsetSlot, buf.writerIndex() - varBlockStart);
            PacketIO.writeVarString(buf, this.path, 4096000);
        }
        else {
            buf.setIntLE(pathOffsetSlot, -1);
        }
        if (this.texture != null) {
            buf.setIntLE(textureOffsetSlot, buf.writerIndex() - varBlockStart);
            PacketIO.writeVarString(buf, this.texture, 4096000);
        }
        else {
            buf.setIntLE(textureOffsetSlot, -1);
        }
        if (this.gradientSet != null) {
            buf.setIntLE(gradientSetOffsetSlot, buf.writerIndex() - varBlockStart);
            PacketIO.writeVarString(buf, this.gradientSet, 4096000);
        }
        else {
            buf.setIntLE(gradientSetOffsetSlot, -1);
        }
        if (this.gradientId != null) {
            buf.setIntLE(gradientIdOffsetSlot, buf.writerIndex() - varBlockStart);
            PacketIO.writeVarString(buf, this.gradientId, 4096000);
        }
        else {
            buf.setIntLE(gradientIdOffsetSlot, -1);
        }
        if (this.camera != null) {
            buf.setIntLE(cameraOffsetSlot, buf.writerIndex() - varBlockStart);
            this.camera.serialize(buf);
        }
        else {
            buf.setIntLE(cameraOffsetSlot, -1);
        }
        if (this.animationSets != null) {
            buf.setIntLE(animationSetsOffsetSlot, buf.writerIndex() - varBlockStart);
            if (this.animationSets.size() > 4096000) {
                throw ProtocolException.dictionaryTooLarge("AnimationSets", this.animationSets.size(), 4096000);
            }
            VarInt.write(buf, this.animationSets.size());
            for (final Map.Entry<String, AnimationSet> e : this.animationSets.entrySet()) {
                PacketIO.writeVarString(buf, e.getKey(), 4096000);
                e.getValue().serialize(buf);
            }
        }
        else {
            buf.setIntLE(animationSetsOffsetSlot, -1);
        }
        if (this.attachments != null) {
            buf.setIntLE(attachmentsOffsetSlot, buf.writerIndex() - varBlockStart);
            if (this.attachments.length > 4096000) {
                throw ProtocolException.arrayTooLong("Attachments", this.attachments.length, 4096000);
            }
            VarInt.write(buf, this.attachments.length);
            for (final ModelAttachment item : this.attachments) {
                item.serialize(buf);
            }
        }
        else {
            buf.setIntLE(attachmentsOffsetSlot, -1);
        }
        if (this.particles != null) {
            buf.setIntLE(particlesOffsetSlot, buf.writerIndex() - varBlockStart);
            if (this.particles.length > 4096000) {
                throw ProtocolException.arrayTooLong("Particles", this.particles.length, 4096000);
            }
            VarInt.write(buf, this.particles.length);
            for (final ModelParticle item2 : this.particles) {
                item2.serialize(buf);
            }
        }
        else {
            buf.setIntLE(particlesOffsetSlot, -1);
        }
        if (this.trails != null) {
            buf.setIntLE(trailsOffsetSlot, buf.writerIndex() - varBlockStart);
            if (this.trails.length > 4096000) {
                throw ProtocolException.arrayTooLong("Trails", this.trails.length, 4096000);
            }
            VarInt.write(buf, this.trails.length);
            for (final ModelTrail item3 : this.trails) {
                item3.serialize(buf);
            }
        }
        else {
            buf.setIntLE(trailsOffsetSlot, -1);
        }
        if (this.detailBoxes != null) {
            buf.setIntLE(detailBoxesOffsetSlot, buf.writerIndex() - varBlockStart);
            if (this.detailBoxes.size() > 4096000) {
                throw ProtocolException.dictionaryTooLarge("DetailBoxes", this.detailBoxes.size(), 4096000);
            }
            VarInt.write(buf, this.detailBoxes.size());
            for (final Map.Entry<String, DetailBox[]> e2 : this.detailBoxes.entrySet()) {
                PacketIO.writeVarString(buf, e2.getKey(), 4096000);
                VarInt.write(buf, e2.getValue().length);
                for (final DetailBox arrItem : e2.getValue()) {
                    arrItem.serialize(buf);
                }
            }
        }
        else {
            buf.setIntLE(detailBoxesOffsetSlot, -1);
        }
        if (this.phobiaModel != null) {
            buf.setIntLE(phobiaModelOffsetSlot, buf.writerIndex() - varBlockStart);
            this.phobiaModel.serialize(buf);
        }
        else {
            buf.setIntLE(phobiaModelOffsetSlot, -1);
        }
    }
    
    public int computeSize() {
        int size = 91;
        if (this.assetId != null) {
            size += PacketIO.stringSize(this.assetId);
        }
        if (this.path != null) {
            size += PacketIO.stringSize(this.path);
        }
        if (this.texture != null) {
            size += PacketIO.stringSize(this.texture);
        }
        if (this.gradientSet != null) {
            size += PacketIO.stringSize(this.gradientSet);
        }
        if (this.gradientId != null) {
            size += PacketIO.stringSize(this.gradientId);
        }
        if (this.camera != null) {
            size += this.camera.computeSize();
        }
        if (this.animationSets != null) {
            int animationSetsSize = 0;
            for (final Map.Entry<String, AnimationSet> kvp : this.animationSets.entrySet()) {
                animationSetsSize += PacketIO.stringSize(kvp.getKey()) + kvp.getValue().computeSize();
            }
            size += VarInt.size(this.animationSets.size()) + animationSetsSize;
        }
        if (this.attachments != null) {
            int attachmentsSize = 0;
            for (final ModelAttachment elem : this.attachments) {
                attachmentsSize += elem.computeSize();
            }
            size += VarInt.size(this.attachments.length) + attachmentsSize;
        }
        if (this.particles != null) {
            int particlesSize = 0;
            for (final ModelParticle elem2 : this.particles) {
                particlesSize += elem2.computeSize();
            }
            size += VarInt.size(this.particles.length) + particlesSize;
        }
        if (this.trails != null) {
            int trailsSize = 0;
            for (final ModelTrail elem3 : this.trails) {
                trailsSize += elem3.computeSize();
            }
            size += VarInt.size(this.trails.length) + trailsSize;
        }
        if (this.detailBoxes != null) {
            int detailBoxesSize = 0;
            for (final Map.Entry<String, DetailBox[]> kvp2 : this.detailBoxes.entrySet()) {
                detailBoxesSize += PacketIO.stringSize(kvp2.getKey()) + VarInt.size(kvp2.getValue().length) + kvp2.getValue().length * 37;
            }
            size += VarInt.size(this.detailBoxes.size()) + detailBoxesSize;
        }
        if (this.phobiaModel != null) {
            size += this.phobiaModel.computeSize();
        }
        return size;
    }
    
    public static ValidationResult validateStructure(@Nonnull final ByteBuf buffer, final int offset) {
        if (buffer.readableBytes() - offset < 91) {
            return ValidationResult.error("Buffer too small: expected at least 91 bytes");
        }
        final byte[] nullBits = PacketIO.readBytes(buffer, offset, 2);
        if ((nullBits[0] & 0x4) != 0x0) {
            final int assetIdOffset = buffer.getIntLE(offset + 43);
            if (assetIdOffset < 0) {
                return ValidationResult.error("Invalid offset for AssetId");
            }
            int pos = offset + 91 + assetIdOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for AssetId");
            }
            final int assetIdLen = VarInt.peek(buffer, pos);
            if (assetIdLen < 0) {
                return ValidationResult.error("Invalid string length for AssetId");
            }
            if (assetIdLen > 4096000) {
                return ValidationResult.error("AssetId exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            pos += assetIdLen;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading AssetId");
            }
        }
        if ((nullBits[0] & 0x8) != 0x0) {
            final int pathOffset = buffer.getIntLE(offset + 47);
            if (pathOffset < 0) {
                return ValidationResult.error("Invalid offset for Path");
            }
            int pos = offset + 91 + pathOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for Path");
            }
            final int pathLen = VarInt.peek(buffer, pos);
            if (pathLen < 0) {
                return ValidationResult.error("Invalid string length for Path");
            }
            if (pathLen > 4096000) {
                return ValidationResult.error("Path exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            pos += pathLen;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading Path");
            }
        }
        if ((nullBits[0] & 0x10) != 0x0) {
            final int textureOffset = buffer.getIntLE(offset + 51);
            if (textureOffset < 0) {
                return ValidationResult.error("Invalid offset for Texture");
            }
            int pos = offset + 91 + textureOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for Texture");
            }
            final int textureLen = VarInt.peek(buffer, pos);
            if (textureLen < 0) {
                return ValidationResult.error("Invalid string length for Texture");
            }
            if (textureLen > 4096000) {
                return ValidationResult.error("Texture exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            pos += textureLen;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading Texture");
            }
        }
        if ((nullBits[0] & 0x20) != 0x0) {
            final int gradientSetOffset = buffer.getIntLE(offset + 55);
            if (gradientSetOffset < 0) {
                return ValidationResult.error("Invalid offset for GradientSet");
            }
            int pos = offset + 91 + gradientSetOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for GradientSet");
            }
            final int gradientSetLen = VarInt.peek(buffer, pos);
            if (gradientSetLen < 0) {
                return ValidationResult.error("Invalid string length for GradientSet");
            }
            if (gradientSetLen > 4096000) {
                return ValidationResult.error("GradientSet exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            pos += gradientSetLen;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading GradientSet");
            }
        }
        if ((nullBits[0] & 0x40) != 0x0) {
            final int gradientIdOffset = buffer.getIntLE(offset + 59);
            if (gradientIdOffset < 0) {
                return ValidationResult.error("Invalid offset for GradientId");
            }
            int pos = offset + 91 + gradientIdOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for GradientId");
            }
            final int gradientIdLen = VarInt.peek(buffer, pos);
            if (gradientIdLen < 0) {
                return ValidationResult.error("Invalid string length for GradientId");
            }
            if (gradientIdLen > 4096000) {
                return ValidationResult.error("GradientId exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            pos += gradientIdLen;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading GradientId");
            }
        }
        if ((nullBits[0] & 0x80) != 0x0) {
            final int cameraOffset = buffer.getIntLE(offset + 63);
            if (cameraOffset < 0) {
                return ValidationResult.error("Invalid offset for Camera");
            }
            int pos = offset + 91 + cameraOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for Camera");
            }
            final ValidationResult cameraResult = CameraSettings.validateStructure(buffer, pos);
            if (!cameraResult.isValid()) {
                return ValidationResult.error("Invalid Camera: " + cameraResult.error());
            }
            pos += CameraSettings.computeBytesConsumed(buffer, pos);
        }
        if ((nullBits[1] & 0x1) != 0x0) {
            final int animationSetsOffset = buffer.getIntLE(offset + 67);
            if (animationSetsOffset < 0) {
                return ValidationResult.error("Invalid offset for AnimationSets");
            }
            int pos = offset + 91 + animationSetsOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for AnimationSets");
            }
            final int animationSetsCount = VarInt.peek(buffer, pos);
            if (animationSetsCount < 0) {
                return ValidationResult.error("Invalid dictionary count for AnimationSets");
            }
            if (animationSetsCount > 4096000) {
                return ValidationResult.error("AnimationSets exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            for (int i = 0; i < animationSetsCount; ++i) {
                final int keyLen = VarInt.peek(buffer, pos);
                if (keyLen < 0) {
                    return ValidationResult.error("Invalid string length for key");
                }
                if (keyLen > 4096000) {
                    return ValidationResult.error("key exceeds max length 4096000");
                }
                pos += VarInt.length(buffer, pos);
                pos += keyLen;
                if (pos > buffer.writerIndex()) {
                    return ValidationResult.error("Buffer overflow reading key");
                }
                pos += AnimationSet.computeBytesConsumed(buffer, pos);
            }
        }
        if ((nullBits[1] & 0x2) != 0x0) {
            final int attachmentsOffset = buffer.getIntLE(offset + 71);
            if (attachmentsOffset < 0) {
                return ValidationResult.error("Invalid offset for Attachments");
            }
            int pos = offset + 91 + attachmentsOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for Attachments");
            }
            final int attachmentsCount = VarInt.peek(buffer, pos);
            if (attachmentsCount < 0) {
                return ValidationResult.error("Invalid array count for Attachments");
            }
            if (attachmentsCount > 4096000) {
                return ValidationResult.error("Attachments exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            for (int i = 0; i < attachmentsCount; ++i) {
                final ValidationResult structResult = ModelAttachment.validateStructure(buffer, pos);
                if (!structResult.isValid()) {
                    return ValidationResult.error("Invalid ModelAttachment in Attachments[" + i + "]: " + structResult.error());
                }
                pos += ModelAttachment.computeBytesConsumed(buffer, pos);
            }
        }
        if ((nullBits[1] & 0x4) != 0x0) {
            final int particlesOffset = buffer.getIntLE(offset + 75);
            if (particlesOffset < 0) {
                return ValidationResult.error("Invalid offset for Particles");
            }
            int pos = offset + 91 + particlesOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for Particles");
            }
            final int particlesCount = VarInt.peek(buffer, pos);
            if (particlesCount < 0) {
                return ValidationResult.error("Invalid array count for Particles");
            }
            if (particlesCount > 4096000) {
                return ValidationResult.error("Particles exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            for (int i = 0; i < particlesCount; ++i) {
                final ValidationResult structResult = ModelParticle.validateStructure(buffer, pos);
                if (!structResult.isValid()) {
                    return ValidationResult.error("Invalid ModelParticle in Particles[" + i + "]: " + structResult.error());
                }
                pos += ModelParticle.computeBytesConsumed(buffer, pos);
            }
        }
        if ((nullBits[1] & 0x8) != 0x0) {
            final int trailsOffset = buffer.getIntLE(offset + 79);
            if (trailsOffset < 0) {
                return ValidationResult.error("Invalid offset for Trails");
            }
            int pos = offset + 91 + trailsOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for Trails");
            }
            final int trailsCount = VarInt.peek(buffer, pos);
            if (trailsCount < 0) {
                return ValidationResult.error("Invalid array count for Trails");
            }
            if (trailsCount > 4096000) {
                return ValidationResult.error("Trails exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            for (int i = 0; i < trailsCount; ++i) {
                final ValidationResult structResult = ModelTrail.validateStructure(buffer, pos);
                if (!structResult.isValid()) {
                    return ValidationResult.error("Invalid ModelTrail in Trails[" + i + "]: " + structResult.error());
                }
                pos += ModelTrail.computeBytesConsumed(buffer, pos);
            }
        }
        if ((nullBits[1] & 0x10) != 0x0) {
            final int detailBoxesOffset = buffer.getIntLE(offset + 83);
            if (detailBoxesOffset < 0) {
                return ValidationResult.error("Invalid offset for DetailBoxes");
            }
            int pos = offset + 91 + detailBoxesOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for DetailBoxes");
            }
            final int detailBoxesCount = VarInt.peek(buffer, pos);
            if (detailBoxesCount < 0) {
                return ValidationResult.error("Invalid dictionary count for DetailBoxes");
            }
            if (detailBoxesCount > 4096000) {
                return ValidationResult.error("DetailBoxes exceeds max length 4096000");
            }
            pos += VarInt.length(buffer, pos);
            for (int i = 0; i < detailBoxesCount; ++i) {
                final int keyLen = VarInt.peek(buffer, pos);
                if (keyLen < 0) {
                    return ValidationResult.error("Invalid string length for key");
                }
                if (keyLen > 4096000) {
                    return ValidationResult.error("key exceeds max length 4096000");
                }
                pos += VarInt.length(buffer, pos);
                pos += keyLen;
                if (pos > buffer.writerIndex()) {
                    return ValidationResult.error("Buffer overflow reading key");
                }
                final int valueArrCount = VarInt.peek(buffer, pos);
                if (valueArrCount < 0) {
                    return ValidationResult.error("Invalid array count for value");
                }
                pos += VarInt.length(buffer, pos);
                for (int valueArrIdx = 0; valueArrIdx < valueArrCount; ++valueArrIdx) {
                    pos += 37;
                }
            }
        }
        if ((nullBits[1] & 0x20) != 0x0) {
            final int phobiaModelOffset = buffer.getIntLE(offset + 87);
            if (phobiaModelOffset < 0) {
                return ValidationResult.error("Invalid offset for PhobiaModel");
            }
            int pos = offset + 91 + phobiaModelOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for PhobiaModel");
            }
            final ValidationResult phobiaModelResult = validateStructure(buffer, pos);
            if (!phobiaModelResult.isValid()) {
                return ValidationResult.error("Invalid PhobiaModel: " + phobiaModelResult.error());
            }
            pos += computeBytesConsumed(buffer, pos);
        }
        return ValidationResult.OK;
    }
    
    public Model clone() {
        final Model copy = new Model();
        copy.assetId = this.assetId;
        copy.path = this.path;
        copy.texture = this.texture;
        copy.gradientSet = this.gradientSet;
        copy.gradientId = this.gradientId;
        copy.camera = ((this.camera != null) ? this.camera.clone() : null);
        copy.scale = this.scale;
        copy.eyeHeight = this.eyeHeight;
        copy.crouchOffset = this.crouchOffset;
        Map.Entry<String, AnimationSet> e = null;
        if (this.animationSets != null) {
            final Map<String, AnimationSet> m = new HashMap<String, AnimationSet>();
            final Iterator<Map.Entry<String, AnimationSet>> iterator = this.animationSets.entrySet().iterator();
            while (iterator.hasNext()) {
                e = iterator.next();
                m.put(e.getKey(), e.getValue().clone());
            }
            copy.animationSets = m;
        }
        copy.attachments = (ModelAttachment[])((this.attachments != null) ? ((ModelAttachment[])Arrays.stream(this.attachments).map(e -> e.clone()).toArray(ModelAttachment[]::new)) : null);
        copy.hitbox = ((this.hitbox != null) ? this.hitbox.clone() : null);
        copy.particles = (ModelParticle[])((this.particles != null) ? ((ModelParticle[])Arrays.stream(this.particles).map(e -> e.clone()).toArray(ModelParticle[]::new)) : null);
        copy.trails = (ModelTrail[])((this.trails != null) ? ((ModelTrail[])Arrays.stream(this.trails).map(e -> e.clone()).toArray(ModelTrail[]::new)) : null);
        copy.light = ((this.light != null) ? this.light.clone() : null);
        if (this.detailBoxes != null) {
            final Map<String, DetailBox[]> i = new HashMap<String, DetailBox[]>();
            for (final Map.Entry<String, DetailBox[]> e2 : this.detailBoxes.entrySet()) {
                i.put(e2.getKey(), Arrays.stream(e2.getValue()).map(x -> x.clone()).toArray(DetailBox[]::new));
            }
            copy.detailBoxes = i;
        }
        copy.phobia = this.phobia;
        copy.phobiaModel = ((this.phobiaModel != null) ? this.phobiaModel.clone() : null);
        return copy;
    }
    
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof final Model other) {
            return Objects.equals(this.assetId, other.assetId) && Objects.equals(this.path, other.path) && Objects.equals(this.texture, other.texture) && Objects.equals(this.gradientSet, other.gradientSet) && Objects.equals(this.gradientId, other.gradientId) && Objects.equals(this.camera, other.camera) && this.scale == other.scale && this.eyeHeight == other.eyeHeight && this.crouchOffset == other.crouchOffset && Objects.equals(this.animationSets, other.animationSets) && Arrays.equals(this.attachments, other.attachments) && Objects.equals(this.hitbox, other.hitbox) && Arrays.equals(this.particles, other.particles) && Arrays.equals(this.trails, other.trails) && Objects.equals(this.light, other.light) && Objects.equals(this.detailBoxes, other.detailBoxes) && Objects.equals(this.phobia, other.phobia) && Objects.equals(this.phobiaModel, other.phobiaModel);
        }
        return false;
    }
    
    @Override
    public int hashCode() {
        int result = 1;
        result = 31 * result + Objects.hashCode(this.assetId);
        result = 31 * result + Objects.hashCode(this.path);
        result = 31 * result + Objects.hashCode(this.texture);
        result = 31 * result + Objects.hashCode(this.gradientSet);
        result = 31 * result + Objects.hashCode(this.gradientId);
        result = 31 * result + Objects.hashCode(this.camera);
        result = 31 * result + Float.hashCode(this.scale);
        result = 31 * result + Float.hashCode(this.eyeHeight);
        result = 31 * result + Float.hashCode(this.crouchOffset);
        result = 31 * result + Objects.hashCode(this.animationSets);
        result = 31 * result + Arrays.hashCode(this.attachments);
        result = 31 * result + Objects.hashCode(this.hitbox);
        result = 31 * result + Arrays.hashCode(this.particles);
        result = 31 * result + Arrays.hashCode(this.trails);
        result = 31 * result + Objects.hashCode(this.light);
        result = 31 * result + Objects.hashCode(this.detailBoxes);
        result = 31 * result + Objects.hashCode(this.phobia);
        result = 31 * result + Objects.hashCode(this.phobiaModel);
        return result;
    }
}
