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

package com.hypixel.hytale.protocol.packets.connection;

import java.util.Objects;
import java.util.Arrays;
import com.hypixel.hytale.protocol.io.ValidationResult;
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 com.hypixel.hytale.protocol.HostAddress;
import javax.annotation.Nullable;
import java.util.UUID;
import javax.annotation.Nonnull;
import com.hypixel.hytale.protocol.Packet;

public class Connect implements Packet
{
    public static final int PACKET_ID = 0;
    public static final boolean IS_COMPRESSED = false;
    public static final int NULLABLE_BIT_FIELD_SIZE = 1;
    public static final int FIXED_BLOCK_SIZE = 46;
    public static final int VARIABLE_FIELD_COUNT = 5;
    public static final int VARIABLE_BLOCK_START = 66;
    public static final int MAX_SIZE = 38013;
    public int protocolCrc;
    public int protocolBuildNumber;
    @Nonnull
    public String clientVersion;
    @Nonnull
    public ClientType clientType;
    @Nonnull
    public UUID uuid;
    @Nonnull
    public String username;
    @Nullable
    public String identityToken;
    @Nonnull
    public String language;
    @Nullable
    public byte[] referralData;
    @Nullable
    public HostAddress referralSource;
    
    @Override
    public int getId() {
        return 0;
    }
    
    public Connect() {
        this.clientVersion = "";
        this.clientType = ClientType.Game;
        this.uuid = new UUID(0L, 0L);
        this.username = "";
        this.language = "";
    }
    
    public Connect(final int protocolCrc, final int protocolBuildNumber, @Nonnull final String clientVersion, @Nonnull final ClientType clientType, @Nonnull final UUID uuid, @Nonnull final String username, @Nullable final String identityToken, @Nonnull final String language, @Nullable final byte[] referralData, @Nullable final HostAddress referralSource) {
        this.clientVersion = "";
        this.clientType = ClientType.Game;
        this.uuid = new UUID(0L, 0L);
        this.username = "";
        this.language = "";
        this.protocolCrc = protocolCrc;
        this.protocolBuildNumber = protocolBuildNumber;
        this.clientVersion = clientVersion;
        this.clientType = clientType;
        this.uuid = uuid;
        this.username = username;
        this.identityToken = identityToken;
        this.language = language;
        this.referralData = referralData;
        this.referralSource = referralSource;
    }
    
    public Connect(@Nonnull final Connect other) {
        this.clientVersion = "";
        this.clientType = ClientType.Game;
        this.uuid = new UUID(0L, 0L);
        this.username = "";
        this.language = "";
        this.protocolCrc = other.protocolCrc;
        this.protocolBuildNumber = other.protocolBuildNumber;
        this.clientVersion = other.clientVersion;
        this.clientType = other.clientType;
        this.uuid = other.uuid;
        this.username = other.username;
        this.identityToken = other.identityToken;
        this.language = other.language;
        this.referralData = other.referralData;
        this.referralSource = other.referralSource;
    }
    
    @Nonnull
    public static Connect deserialize(@Nonnull final ByteBuf buf, final int offset) {
        final Connect obj = new Connect();
        final byte nullBits = buf.getByte(offset);
        obj.protocolCrc = buf.getIntLE(offset + 1);
        obj.protocolBuildNumber = buf.getIntLE(offset + 5);
        obj.clientVersion = PacketIO.readFixedAsciiString(buf, offset + 9, 20);
        obj.clientType = ClientType.fromValue(buf.getByte(offset + 29));
        obj.uuid = PacketIO.readUUID(buf, offset + 30);
        final int varPos0 = offset + 66 + buf.getIntLE(offset + 46);
        final int usernameLen = VarInt.peek(buf, varPos0);
        if (usernameLen < 0) {
            throw ProtocolException.negativeLength("Username", usernameLen);
        }
        if (usernameLen > 16) {
            throw ProtocolException.stringTooLong("Username", usernameLen, 16);
        }
        obj.username = PacketIO.readVarString(buf, varPos0, PacketIO.ASCII);
        if ((nullBits & 0x1) != 0x0) {
            final int varPos2 = offset + 66 + buf.getIntLE(offset + 50);
            final int identityTokenLen = VarInt.peek(buf, varPos2);
            if (identityTokenLen < 0) {
                throw ProtocolException.negativeLength("IdentityToken", identityTokenLen);
            }
            if (identityTokenLen > 8192) {
                throw ProtocolException.stringTooLong("IdentityToken", identityTokenLen, 8192);
            }
            obj.identityToken = PacketIO.readVarString(buf, varPos2, PacketIO.UTF8);
        }
        final int varPos3 = offset + 66 + buf.getIntLE(offset + 54);
        final int languageLen = VarInt.peek(buf, varPos3);
        if (languageLen < 0) {
            throw ProtocolException.negativeLength("Language", languageLen);
        }
        if (languageLen > 16) {
            throw ProtocolException.stringTooLong("Language", languageLen, 16);
        }
        obj.language = PacketIO.readVarString(buf, varPos3, PacketIO.ASCII);
        if ((nullBits & 0x2) != 0x0) {
            final int varPos4 = offset + 66 + buf.getIntLE(offset + 58);
            final int referralDataCount = VarInt.peek(buf, varPos4);
            if (referralDataCount < 0) {
                throw ProtocolException.negativeLength("ReferralData", referralDataCount);
            }
            if (referralDataCount > 4096) {
                throw ProtocolException.arrayTooLong("ReferralData", referralDataCount, 4096);
            }
            final int varIntLen = VarInt.length(buf, varPos4);
            if (varPos4 + varIntLen + referralDataCount * 1L > buf.readableBytes()) {
                throw ProtocolException.bufferTooSmall("ReferralData", varPos4 + varIntLen + referralDataCount * 1, buf.readableBytes());
            }
            obj.referralData = new byte[referralDataCount];
            for (int i = 0; i < referralDataCount; ++i) {
                obj.referralData[i] = buf.getByte(varPos4 + varIntLen + i * 1);
            }
        }
        if ((nullBits & 0x4) != 0x0) {
            final int varPos5 = offset + 66 + buf.getIntLE(offset + 62);
            obj.referralSource = HostAddress.deserialize(buf, varPos5);
        }
        return obj;
    }
    
    public static int computeBytesConsumed(@Nonnull final ByteBuf buf, final int offset) {
        final byte nullBits = buf.getByte(offset);
        int maxEnd = 66;
        final int fieldOffset0 = buf.getIntLE(offset + 46);
        int pos0 = offset + 66 + fieldOffset0;
        int sl = VarInt.peek(buf, pos0);
        pos0 += VarInt.length(buf, pos0) + sl;
        if (pos0 - offset > maxEnd) {
            maxEnd = pos0 - offset;
        }
        if ((nullBits & 0x1) != 0x0) {
            final int fieldOffset2 = buf.getIntLE(offset + 50);
            int pos2 = offset + 66 + fieldOffset2;
            sl = VarInt.peek(buf, pos2);
            pos2 += VarInt.length(buf, pos2) + sl;
            if (pos2 - offset > maxEnd) {
                maxEnd = pos2 - offset;
            }
        }
        final int fieldOffset3 = buf.getIntLE(offset + 54);
        int pos3 = offset + 66 + fieldOffset3;
        sl = VarInt.peek(buf, pos3);
        pos3 += VarInt.length(buf, pos3) + sl;
        if (pos3 - offset > maxEnd) {
            maxEnd = pos3 - offset;
        }
        if ((nullBits & 0x2) != 0x0) {
            final int fieldOffset4 = buf.getIntLE(offset + 58);
            int pos4 = offset + 66 + fieldOffset4;
            final int arrLen = VarInt.peek(buf, pos4);
            pos4 += VarInt.length(buf, pos4) + arrLen * 1;
            if (pos4 - offset > maxEnd) {
                maxEnd = pos4 - offset;
            }
        }
        if ((nullBits & 0x4) != 0x0) {
            final int fieldOffset5 = buf.getIntLE(offset + 62);
            int pos5 = offset + 66 + fieldOffset5;
            pos5 += HostAddress.computeBytesConsumed(buf, pos5);
            if (pos5 - offset > maxEnd) {
                maxEnd = pos5 - offset;
            }
        }
        return maxEnd;
    }
    
    @Override
    public void serialize(@Nonnull final ByteBuf buf) {
        final int startPos = buf.writerIndex();
        byte nullBits = 0;
        if (this.identityToken != null) {
            nullBits |= 0x1;
        }
        if (this.referralData != null) {
            nullBits |= 0x2;
        }
        if (this.referralSource != null) {
            nullBits |= 0x4;
        }
        buf.writeByte(nullBits);
        buf.writeIntLE(this.protocolCrc);
        buf.writeIntLE(this.protocolBuildNumber);
        PacketIO.writeFixedAsciiString(buf, this.clientVersion, 20);
        buf.writeByte(this.clientType.getValue());
        PacketIO.writeUUID(buf, this.uuid);
        final int usernameOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int identityTokenOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int languageOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int referralDataOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int referralSourceOffsetSlot = buf.writerIndex();
        buf.writeIntLE(0);
        final int varBlockStart = buf.writerIndex();
        buf.setIntLE(usernameOffsetSlot, buf.writerIndex() - varBlockStart);
        PacketIO.writeVarAsciiString(buf, this.username, 16);
        if (this.identityToken != null) {
            buf.setIntLE(identityTokenOffsetSlot, buf.writerIndex() - varBlockStart);
            PacketIO.writeVarString(buf, this.identityToken, 8192);
        }
        else {
            buf.setIntLE(identityTokenOffsetSlot, -1);
        }
        buf.setIntLE(languageOffsetSlot, buf.writerIndex() - varBlockStart);
        PacketIO.writeVarAsciiString(buf, this.language, 16);
        if (this.referralData != null) {
            buf.setIntLE(referralDataOffsetSlot, buf.writerIndex() - varBlockStart);
            if (this.referralData.length > 4096) {
                throw ProtocolException.arrayTooLong("ReferralData", this.referralData.length, 4096);
            }
            VarInt.write(buf, this.referralData.length);
            for (final byte item : this.referralData) {
                buf.writeByte(item);
            }
        }
        else {
            buf.setIntLE(referralDataOffsetSlot, -1);
        }
        if (this.referralSource != null) {
            buf.setIntLE(referralSourceOffsetSlot, buf.writerIndex() - varBlockStart);
            this.referralSource.serialize(buf);
        }
        else {
            buf.setIntLE(referralSourceOffsetSlot, -1);
        }
    }
    
    @Override
    public int computeSize() {
        int size = 66;
        size += VarInt.size(this.username.length()) + this.username.length();
        if (this.identityToken != null) {
            size += PacketIO.stringSize(this.identityToken);
        }
        size += VarInt.size(this.language.length()) + this.language.length();
        if (this.referralData != null) {
            size += VarInt.size(this.referralData.length) + this.referralData.length * 1;
        }
        if (this.referralSource != null) {
            size += this.referralSource.computeSize();
        }
        return size;
    }
    
    public static ValidationResult validateStructure(@Nonnull final ByteBuf buffer, final int offset) {
        if (buffer.readableBytes() - offset < 66) {
            return ValidationResult.error("Buffer too small: expected at least 66 bytes");
        }
        final byte nullBits = buffer.getByte(offset);
        final int usernameOffset = buffer.getIntLE(offset + 46);
        if (usernameOffset < 0) {
            return ValidationResult.error("Invalid offset for Username");
        }
        int pos = offset + 66 + usernameOffset;
        if (pos >= buffer.writerIndex()) {
            return ValidationResult.error("Offset out of bounds for Username");
        }
        final int usernameLen = VarInt.peek(buffer, pos);
        if (usernameLen < 0) {
            return ValidationResult.error("Invalid string length for Username");
        }
        if (usernameLen > 16) {
            return ValidationResult.error("Username exceeds max length 16");
        }
        pos += VarInt.length(buffer, pos);
        pos += usernameLen;
        if (pos > buffer.writerIndex()) {
            return ValidationResult.error("Buffer overflow reading Username");
        }
        if ((nullBits & 0x1) != 0x0) {
            final int identityTokenOffset = buffer.getIntLE(offset + 50);
            if (identityTokenOffset < 0) {
                return ValidationResult.error("Invalid offset for IdentityToken");
            }
            pos = offset + 66 + identityTokenOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for IdentityToken");
            }
            final int identityTokenLen = VarInt.peek(buffer, pos);
            if (identityTokenLen < 0) {
                return ValidationResult.error("Invalid string length for IdentityToken");
            }
            if (identityTokenLen > 8192) {
                return ValidationResult.error("IdentityToken exceeds max length 8192");
            }
            pos += VarInt.length(buffer, pos);
            pos += identityTokenLen;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading IdentityToken");
            }
        }
        final int languageOffset = buffer.getIntLE(offset + 54);
        if (languageOffset < 0) {
            return ValidationResult.error("Invalid offset for Language");
        }
        pos = offset + 66 + languageOffset;
        if (pos >= buffer.writerIndex()) {
            return ValidationResult.error("Offset out of bounds for Language");
        }
        final int languageLen = VarInt.peek(buffer, pos);
        if (languageLen < 0) {
            return ValidationResult.error("Invalid string length for Language");
        }
        if (languageLen > 16) {
            return ValidationResult.error("Language exceeds max length 16");
        }
        pos += VarInt.length(buffer, pos);
        pos += languageLen;
        if (pos > buffer.writerIndex()) {
            return ValidationResult.error("Buffer overflow reading Language");
        }
        if ((nullBits & 0x2) != 0x0) {
            final int referralDataOffset = buffer.getIntLE(offset + 58);
            if (referralDataOffset < 0) {
                return ValidationResult.error("Invalid offset for ReferralData");
            }
            pos = offset + 66 + referralDataOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for ReferralData");
            }
            final int referralDataCount = VarInt.peek(buffer, pos);
            if (referralDataCount < 0) {
                return ValidationResult.error("Invalid array count for ReferralData");
            }
            if (referralDataCount > 4096) {
                return ValidationResult.error("ReferralData exceeds max length 4096");
            }
            pos += VarInt.length(buffer, pos);
            pos += referralDataCount * 1;
            if (pos > buffer.writerIndex()) {
                return ValidationResult.error("Buffer overflow reading ReferralData");
            }
        }
        if ((nullBits & 0x4) != 0x0) {
            final int referralSourceOffset = buffer.getIntLE(offset + 62);
            if (referralSourceOffset < 0) {
                return ValidationResult.error("Invalid offset for ReferralSource");
            }
            pos = offset + 66 + referralSourceOffset;
            if (pos >= buffer.writerIndex()) {
                return ValidationResult.error("Offset out of bounds for ReferralSource");
            }
            final ValidationResult referralSourceResult = HostAddress.validateStructure(buffer, pos);
            if (!referralSourceResult.isValid()) {
                return ValidationResult.error("Invalid ReferralSource: " + referralSourceResult.error());
            }
            pos += HostAddress.computeBytesConsumed(buffer, pos);
        }
        return ValidationResult.OK;
    }
    
    public Connect clone() {
        final Connect copy = new Connect();
        copy.protocolCrc = this.protocolCrc;
        copy.protocolBuildNumber = this.protocolBuildNumber;
        copy.clientVersion = this.clientVersion;
        copy.clientType = this.clientType;
        copy.uuid = this.uuid;
        copy.username = this.username;
        copy.identityToken = this.identityToken;
        copy.language = this.language;
        copy.referralData = (byte[])((this.referralData != null) ? Arrays.copyOf(this.referralData, this.referralData.length) : null);
        copy.referralSource = ((this.referralSource != null) ? this.referralSource.clone() : null);
        return copy;
    }
    
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof final Connect other) {
            return this.protocolCrc == other.protocolCrc && this.protocolBuildNumber == other.protocolBuildNumber && Objects.equals(this.clientVersion, other.clientVersion) && Objects.equals(this.clientType, other.clientType) && Objects.equals(this.uuid, other.uuid) && Objects.equals(this.username, other.username) && Objects.equals(this.identityToken, other.identityToken) && Objects.equals(this.language, other.language) && Arrays.equals(this.referralData, other.referralData) && Objects.equals(this.referralSource, other.referralSource);
        }
        return false;
    }
    
    @Override
    public int hashCode() {
        int result = 1;
        result = 31 * result + Integer.hashCode(this.protocolCrc);
        result = 31 * result + Integer.hashCode(this.protocolBuildNumber);
        result = 31 * result + Objects.hashCode(this.clientVersion);
        result = 31 * result + Objects.hashCode(this.clientType);
        result = 31 * result + Objects.hashCode(this.uuid);
        result = 31 * result + Objects.hashCode(this.username);
        result = 31 * result + Objects.hashCode(this.identityToken);
        result = 31 * result + Objects.hashCode(this.language);
        result = 31 * result + Arrays.hashCode(this.referralData);
        result = 31 * result + Objects.hashCode(this.referralSource);
        return result;
    }
}
