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

package com.hypixel.hytale.codec.codecs.array;

import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.schema.SchemaConvertable;
import com.hypixel.hytale.codec.schema.config.ArraySchema;
import com.hypixel.hytale.codec.schema.config.Schema;
import com.hypixel.hytale.codec.schema.SchemaContext;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.io.IOException;
import java.util.Arrays;
import com.hypixel.hytale.codec.util.RawJsonReader;
import org.bson.BsonNull;
import org.bson.BsonArray;
import com.hypixel.hytale.codec.exception.CodecException;
import com.hypixel.hytale.codec.ExtraInfo;
import javax.annotation.Nonnull;
import org.bson.BsonValue;
import com.hypixel.hytale.codec.schema.metadata.Metadata;
import java.util.List;
import javax.annotation.Nullable;
import java.util.function.Supplier;
import java.util.function.IntFunction;
import com.hypixel.hytale.codec.WrappedCodec;
import com.hypixel.hytale.codec.RawJsonCodec;
import com.hypixel.hytale.codec.Codec;

public class ArrayCodec<T> implements Codec<T[]>, RawJsonCodec<T[]>, WrappedCodec<T>
{
    private final Codec<T> codec;
    private final IntFunction<T[]> arrayConstructor;
    @Nullable
    private final Supplier<T> defaultValue;
    private List<Metadata> metadata;
    private T[] emptyArray;
    
    public ArrayCodec(final Codec<T> codec, final IntFunction<T[]> arrayConstructor) {
        this(codec, arrayConstructor, null);
    }
    
    public ArrayCodec(final Codec<T> codec, final IntFunction<T[]> arrayConstructor, @Nullable final Supplier<T> defaultValue) {
        this.codec = codec;
        this.arrayConstructor = arrayConstructor;
        this.defaultValue = defaultValue;
    }
    
    @Override
    public Codec<T> getChildCodec() {
        return this.codec;
    }
    
    @Override
    public T[] decode(@Nonnull final BsonValue bsonValue, @Nonnull final ExtraInfo extraInfo) {
        final BsonArray bsonArray = bsonValue.asArray();
        final T[] array = this.arrayConstructor.apply(bsonArray.size());
        for (int i = 0, size = bsonArray.size(); i < size; ++i) {
            final BsonValue value = bsonArray.get(i);
            extraInfo.pushIntKey(i);
            try {
                array[i] = this.decodeElement(value, extraInfo);
            }
            catch (final Exception e) {
                throw new CodecException("Failed to decode", value, extraInfo, e);
            }
            finally {
                extraInfo.popKey();
            }
        }
        return array;
    }
    
    @Nonnull
    @Override
    public BsonValue encode(@Nonnull final T[] array, final ExtraInfo extraInfo) {
        final BsonArray bsonArray = new BsonArray();
        for (final T t : array) {
            if (t == null) {
                bsonArray.add(new BsonNull());
            }
            else {
                bsonArray.add(this.codec.encode(t, extraInfo));
            }
        }
        return bsonArray;
    }
    
    @Override
    public T[] decodeJson(@Nonnull final RawJsonReader reader, @Nonnull final ExtraInfo extraInfo) throws IOException {
        reader.expect('[');
        reader.consumeWhiteSpace();
        if (reader.tryConsume(']')) {
            if (this.emptyArray == null) {
                this.emptyArray = this.arrayConstructor.apply(0);
            }
            return this.emptyArray;
        }
        int i = 0;
        T[] arr = this.arrayConstructor.apply(10);
        while (true) {
            if (i == arr.length) {
                arr = Arrays.copyOf(arr, i + 1 + (i >> 1));
            }
            extraInfo.pushIntKey(i, reader);
            try {
                arr[i] = this.decodeJsonElement(reader, extraInfo);
                ++i;
            }
            catch (final Exception e) {
                throw new CodecException("Failed to decode", reader, extraInfo, e);
            }
            finally {
                extraInfo.popKey();
            }
            reader.consumeWhiteSpace();
            if (reader.tryConsumeOrExpect(']', ',')) {
                break;
            }
            reader.consumeWhiteSpace();
        }
        if (arr.length == i) {
            return arr;
        }
        return Arrays.copyOf(arr, i);
    }
    
    @Nonnull
    public ArrayCodec<T> metadata(final Metadata metadata) {
        if (this.metadata == null) {
            this.metadata = new ObjectArrayList<Metadata>();
        }
        this.metadata.add(metadata);
        return this;
    }
    
    @Nonnull
    @Override
    public Schema toSchema(@Nonnull final SchemaContext context) {
        final ArraySchema arraySchema = new ArraySchema();
        final Schema childSchema = context.refDefinition(this.codec);
        if (this.metadata != null) {
            for (int i = 0; i < this.metadata.size(); ++i) {
                final Metadata meta = this.metadata.get(i);
                meta.modify(childSchema);
            }
        }
        arraySchema.setItem(childSchema);
        return arraySchema;
    }
    
    @Nullable
    public Supplier<T> getDefaultSupplier() {
        return this.defaultValue;
    }
    
    @Nullable
    protected T decodeElement(@Nonnull final BsonValue value, final ExtraInfo extraInfo) {
        if (!value.isNull()) {
            return this.codec.decode(value, extraInfo);
        }
        return (this.defaultValue == null) ? null : this.defaultValue.get();
    }
    
    @Nullable
    protected T decodeJsonElement(@Nonnull final RawJsonReader reader, final ExtraInfo extraInfo) throws IOException {
        if (!reader.tryConsume("null")) {
            return this.codec.decodeJson(reader, extraInfo);
        }
        return (this.defaultValue == null) ? null : this.defaultValue.get();
    }
    
    @Nonnull
    public static <T> ArrayCodec<T> ofBuilderCodec(@Nonnull final BuilderCodec<T> codec, final IntFunction<T[]> arrayConstructor) {
        return new ArrayCodec<T>(codec, arrayConstructor, codec.getSupplier());
    }
}
