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

package com.hypixel.hytale.codec.builder;

import java.util.function.Supplier;
import com.hypixel.hytale.codec.validation.LegacyValidator;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.codec.schema.config.Schema;
import com.hypixel.hytale.codec.schema.SchemaContext;
import com.hypixel.hytale.codec.validation.LateValidator;
import com.hypixel.hytale.codec.validation.ValidatableCodec;
import com.hypixel.hytale.codec.validation.validator.DeprecatedValidator;
import java.util.Set;
import java.util.function.Predicate;
import com.hypixel.hytale.codec.validation.ValidationResults;
import com.hypixel.hytale.codec.validation.Validators;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.InheritCodec;
import java.io.IOException;
import com.hypixel.hytale.codec.util.RawJsonReader;
import org.bson.BsonDocument;
import java.util.Objects;
import java.util.Iterator;
import com.hypixel.hytale.codec.PrimitiveCodec;
import com.hypixel.hytale.codec.validation.validator.NonNullValidator;
import com.hypixel.hytale.codec.schema.metadata.Metadata;
import javax.annotation.Nullable;
import com.hypixel.hytale.codec.validation.Validator;
import java.util.List;
import java.util.function.BiFunction;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.function.consumer.TriConsumer;
import javax.annotation.Nonnull;
import com.hypixel.hytale.codec.KeyedCodec;

public class BuilderField<Type, FieldType>
{
    @Nonnull
    protected final KeyedCodec<FieldType> codec;
    @Nonnull
    protected final TriConsumer<Type, FieldType, ExtraInfo> setter;
    @Nonnull
    protected final BiFunction<Type, ExtraInfo, FieldType> getter;
    protected final TriConsumer<Type, Type, ExtraInfo> inherit;
    @Nullable
    protected final List<Validator<? super FieldType>> validators;
    @Nullable
    protected final List<Metadata> metadata;
    protected final int minVersion;
    protected final int maxVersion;
    @Nullable
    protected final String documentation;
    @Nullable
    protected final NonNullValidator<? super FieldType> nonNullValidator;
    protected final boolean isPrimitive;
    
    protected BuilderField(@Nonnull final FieldBuilder<Type, FieldType, ?> builder) {
        this.codec = builder.codec;
        this.setter = builder.setter;
        this.getter = builder.getter;
        this.inherit = builder.inherit;
        this.validators = builder.validators;
        this.metadata = builder.metadata;
        this.minVersion = builder.minVersion;
        this.maxVersion = builder.maxVersion;
        this.documentation = builder.documentation;
        if (builder.validators == null) {
            this.nonNullValidator = null;
        }
        else {
            NonNullValidator<? super FieldType> found = null;
            for (final Validator<? super FieldType> validator : builder.validators) {
                if (validator instanceof NonNullValidator) {
                    found = (NonNullValidator)validator;
                    break;
                }
            }
            this.nonNullValidator = found;
        }
        this.isPrimitive = (this.codec.getChildCodec() instanceof PrimitiveCodec);
    }
    
    protected BuilderField(@Nonnull final KeyedCodec<FieldType> codec, final TriConsumer<Type, FieldType, ExtraInfo> setter, final BiFunction<Type, ExtraInfo, FieldType> getter, final TriConsumer<Type, Type, ExtraInfo> inherit) {
        this.codec = Objects.requireNonNull(codec, "codec parameter can't be null");
        this.setter = Objects.requireNonNull(setter, "setter parameter can't be null");
        this.getter = Objects.requireNonNull(getter, "getter parameter can't be null");
        this.inherit = inherit;
        this.validators = null;
        this.metadata = null;
        this.minVersion = Integer.MIN_VALUE;
        this.maxVersion = Integer.MAX_VALUE;
        this.documentation = null;
        this.nonNullValidator = null;
        this.isPrimitive = (codec.getChildCodec() instanceof PrimitiveCodec);
    }
    
    @Nonnull
    public KeyedCodec<FieldType> getCodec() {
        return this.codec;
    }
    
    public int getMinVersion() {
        return this.minVersion;
    }
    
    public int getMaxVersion() {
        return this.maxVersion;
    }
    
    public int getHighestSupportedVersion() {
        return (this.maxVersion != Integer.MAX_VALUE) ? this.maxVersion : this.minVersion;
    }
    
    public boolean supportsVersion(final int version) {
        return version == Integer.MIN_VALUE || (version >= this.minVersion && version <= this.maxVersion);
    }
    
    @Nullable
    public List<Validator<? super FieldType>> getValidators() {
        return this.validators;
    }
    
    public boolean hasNonNullValidator() {
        return this.nonNullValidator != null;
    }
    
    @Nullable
    public String getDocumentation() {
        return this.documentation;
    }
    
    public void decode(final BsonDocument document, final Type t, @Nonnull final ExtraInfo extraInfo) {
        final FieldType value = this.codec.getOrNull(document, extraInfo);
        this.setValue(t, value, extraInfo);
    }
    
    public void decodeAndInherit(final BsonDocument document, final Type t, @Nullable final Type parent, @Nonnull final ExtraInfo extraInfo) {
        final FieldType parentValue = (parent != null) ? this.getter.apply(parent, extraInfo) : null;
        final FieldType value = this.codec.getAndInherit(document, parentValue, extraInfo).orElse(null);
        this.setValue(t, value, extraInfo);
    }
    
    public void encode(@Nonnull final BsonDocument document, final Type t, @Nonnull final ExtraInfo extraInfo) {
        final FieldType value = this.getter.apply(t, extraInfo);
        if (value != null) {
            this.codec.put(document, value, extraInfo);
        }
    }
    
    public void inherit(final Type t, final Type parent, final ExtraInfo extraInfo) {
        if (this.inherit != null) {
            this.inherit.accept(t, parent, extraInfo);
        }
    }
    
    public void decodeJson(@Nonnull final RawJsonReader reader, final Type t, @Nonnull final ExtraInfo extraInfo) throws IOException {
        final int read = reader.peek();
        if (read == -1) {
            throw new IOException("Unexpected EOF!");
        }
        switch (read) {
            case 78:
            case 110: {
                reader.readNullValue();
                this.setValue(t, null, extraInfo);
                return;
            }
            default: {
                final FieldType value = this.codec.getChildCodec().decodeJson(reader, extraInfo);
                this.setValue(t, value, extraInfo);
            }
        }
    }
    
    public void decodeAndInheritJson(@Nonnull final RawJsonReader reader, final Type t, @Nullable final Type parent, @Nonnull final ExtraInfo extraInfo) throws IOException {
        final int read = reader.peek();
        if (read == -1) {
            throw new IOException("Unexpected EOF!");
        }
        switch (read) {
            case 78:
            case 110: {
                reader.readNullValue();
                this.setValue(t, null, extraInfo);
                return;
            }
            default: {
                final Codec<FieldType> child = this.codec.getChildCodec();
                if (child instanceof InheritCodec) {
                    final FieldType parentValue = (parent != null) ? this.getter.apply(parent, extraInfo) : null;
                    final FieldType value = (FieldType)((InheritCodec)child).decodeAndInheritJson(reader, parentValue, extraInfo);
                    this.setValue(t, value, extraInfo);
                }
                else {
                    final FieldType value2 = child.decodeJson(reader, extraInfo);
                    this.setValue(t, value2, extraInfo);
                }
            }
        }
    }
    
    public void setValue(final Type t, @Nullable final FieldType value, @Nonnull final ExtraInfo extraInfo) {
        if (this.validators != null) {
            final ValidationResults results = extraInfo.getValidationResults();
            for (int i = 0; i < this.validators.size(); ++i) {
                this.validators.get(i).accept((Object)value, results);
            }
            results._processValidationResults();
        }
        if (this.isPrimitive && value == null) {
            final ValidationResults results = extraInfo.getValidationResults();
            Validators.nonNull().accept((Object)null, results);
            results._processValidationResults();
            return;
        }
        this.setter.accept(t, value, extraInfo);
    }
    
    public void validate(final Type t, @Nonnull final ExtraInfo extraInfo) {
        final FieldType value = this.getter.apply(t, extraInfo);
        this.validateValue(value, extraInfo, null);
    }
    
    public void validateDefaults(final Type t, @Nonnull final ExtraInfo extraInfo, final Set<Codec<?>> tested) {
        final FieldType defaultValue = this.getter.apply(t, extraInfo);
        if (defaultValue != null && !this.codec.isRequired() && this.nonNullValidator == null) {
            this.validateValue(defaultValue, extraInfo, v -> v instanceof DeprecatedValidator);
        }
        final Codec<FieldType> childCodec = this.codec.getChildCodec();
        ValidatableCodec.validateDefaults(childCodec, extraInfo, tested);
    }
    
    private void validateValue(final FieldType value, @Nonnull final ExtraInfo extraInfo, @Nullable final Predicate<Validator<? super FieldType>> filter) {
        if (this.codec instanceof final ValidatableCodec validatableCodec) {
            validatableCodec.validate(value, extraInfo);
        }
        if (this.validators != null) {
            final ValidationResults results = extraInfo.getValidationResults();
            for (int i = 0; i < this.validators.size(); ++i) {
                final Validator<? super FieldType> validator = this.validators.get(i);
                if (filter == null || !filter.test(validator)) {
                    if (!(validator instanceof LateValidator)) {
                        validator.accept((Object)value, results);
                    }
                }
            }
            results._processValidationResults();
        }
    }
    
    public void nullValidate(final Type t, @Nonnull final ValidationResults results, final ExtraInfo extraInfo) {
        if (this.nonNullValidator == null) {
            return;
        }
        final FieldType apply = this.getter.apply(t, extraInfo);
        if (apply != null) {
            return;
        }
        this.nonNullValidator.accept((Object)null, results);
        results._processValidationResults();
    }
    
    public void updateSchema(final SchemaContext context, @Nonnull final Schema target) {
        if (this.validators != null) {
            for (int i = 0; i < this.validators.size(); ++i) {
                final Validator<? super FieldType> validator = this.validators.get(i);
                validator.updateSchema(context, target);
            }
        }
        if (this.metadata != null) {
            for (int i = 0; i < this.metadata.size(); ++i) {
                final Metadata meta = this.metadata.get(i);
                meta.modify(target);
            }
        }
        if (this.inherit != null) {
            target.getHytale().setInheritsProperty(true);
        }
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "BuilderField{codec=" + String.valueOf(this.codec) + ", setter=" + String.valueOf(this.setter) + ", getter=" + String.valueOf(this.getter);
    }
    
    public static class FieldBuilder<T, FieldType, Builder extends BuilderCodec.BuilderBase<T, Builder>>
    {
        @Nonnull
        protected final Builder parentBuilder;
        @Nonnull
        protected final KeyedCodec<FieldType> codec;
        @Nonnull
        protected final TriConsumer<T, FieldType, ExtraInfo> setter;
        @Nonnull
        protected final BiFunction<T, ExtraInfo, FieldType> getter;
        protected final TriConsumer<T, T, ExtraInfo> inherit;
        protected List<Validator<? super FieldType>> validators;
        protected List<Metadata> metadata;
        protected int minVersion;
        protected int maxVersion;
        protected String documentation;
        
        public FieldBuilder(final Builder parentBuilder, final KeyedCodec<FieldType> codec, final TriConsumer<T, FieldType, ExtraInfo> setter, final BiFunction<T, ExtraInfo, FieldType> getter, final TriConsumer<T, T, ExtraInfo> inherit) {
            this.minVersion = Integer.MIN_VALUE;
            this.maxVersion = Integer.MAX_VALUE;
            this.parentBuilder = Objects.requireNonNull(parentBuilder, "parentBuilder parameter can't be null");
            this.codec = Objects.requireNonNull(codec, "codec parameter can't be null");
            this.setter = Objects.requireNonNull(setter, "setter parameter can't be null");
            this.getter = Objects.requireNonNull(getter, "getter parameter can't be null");
            this.inherit = inherit;
        }
        
        @Nonnull
        public FieldBuilder<T, FieldType, Builder> addValidator(final Validator<? super FieldType> validator) {
            if (this.validators == null) {
                this.validators = new ObjectArrayList<Validator<? super FieldType>>();
            }
            this.validators.add(validator);
            return this;
        }
        
        @Nonnull
        @Deprecated(forRemoval = true)
        public FieldBuilder<T, FieldType, Builder> addValidator(final LegacyValidator<? super FieldType> validator) {
            if (this.validators == null) {
                this.validators = new ObjectArrayList<Validator<? super FieldType>>();
            }
            this.validators.add(validator);
            return this;
        }
        
        @Nonnull
        public FieldBuilder<T, FieldType, Builder> addValidatorLate(@Nonnull final Supplier<LateValidator<? super FieldType>> validatorSupplier) {
            if (this.validators == null) {
                this.validators = new ObjectArrayList<Validator<? super FieldType>>();
            }
            this.validators.add(new LateValidator<FieldType>(this) {
                private LateValidator<? super FieldType> validator;
                
                @Override
                public void accept(final FieldType fieldType, final ValidationResults results) {
                    if (this.validator == null) {
                        this.validator = validatorSupplier.get();
                    }
                    this.validator.accept((Object)fieldType, results);
                }
                
                @Override
                public void acceptLate(final FieldType fieldType, final ValidationResults results, final ExtraInfo extraInfo) {
                    if (this.validator == null) {
                        this.validator = validatorSupplier.get();
                    }
                    this.validator.acceptLate(fieldType, results, extraInfo);
                }
                
                @Override
                public void updateSchema(final SchemaContext context, final Schema target) {
                    if (this.validator == null) {
                        this.validator = validatorSupplier.get();
                    }
                    this.validator.updateSchema(context, target);
                }
            });
            return this;
        }
        
        @Nonnull
        public FieldBuilder<T, FieldType, Builder> setVersionRange(final int minVersion, final int maxVersion) {
            this.minVersion = minVersion;
            this.maxVersion = maxVersion;
            return this;
        }
        
        @Nonnull
        public FieldBuilder<T, FieldType, Builder> documentation(final String doc) {
            this.documentation = doc;
            return this;
        }
        
        @Nonnull
        public FieldBuilder<T, FieldType, Builder> metadata(final Metadata metadata) {
            if (this.metadata == null) {
                this.metadata = new ObjectArrayList<Metadata>();
            }
            this.metadata.add(metadata);
            return this;
        }
        
        @Nonnull
        public Builder add() {
            ((BuilderCodec.BuilderBase<T, BuilderCodec.BuilderBase>)this.parentBuilder).addField(new BuilderField<T, Object>((FieldBuilder<T, Object, ?>)this));
            return this.parentBuilder;
        }
    }
}
