package net.minecraft.world.level.block.state;

import javax.annotation.Nullable;
import com.mojang.serialization.Codec;
import com.google.common.collect.Maps;
import com.google.common.collect.UnmodifiableIterator;
import com.google.common.collect.ArrayTable;
import com.google.common.collect.HashBasedTable;
import java.util.Optional;
import java.util.Collections;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.Iterator;
import java.util.Collection;
import com.mojang.serialization.MapCodec;
import com.google.common.collect.Table;
import com.google.common.collect.ImmutableMap;
import net.minecraft.world.level.block.state.properties.Property;
import java.util.Map;
import java.util.function.Function;

public abstract class StateHolder<O, S> {
    private static final Function<Map.Entry<Property<?>, Comparable<?>>, String> PROPERTY_ENTRY_TO_STRING_FUNCTION;
    protected final O owner;
    private final ImmutableMap<Property<?>, Comparable<?>> values;
    private Table<Property<?>, Comparable<?>, S> neighbours;
    protected final MapCodec<S> propertiesCodec;
    
    protected StateHolder(final O object, final ImmutableMap<Property<?>, Comparable<?>> immutableMap, final MapCodec<S> mapCodec) {
        this.owner = object;
        this.values = immutableMap;
        this.propertiesCodec = mapCodec;
    }
    
    public <T extends Comparable<T>> S cycle(final Property<T> cgl) {
        return this.<T, Comparable>setValue(cgl, (Comparable)StateHolder.<V>findNextInCollection((Collection<V>)cgl.getPossibleValues(), (V)this.<T>getValue((Property<T>)cgl)));
    }
    
    protected static <T> T findNextInCollection(final Collection<T> collection, final T object) {
        final Iterator<T> iterator3 = collection.iterator();
        while (iterator3.hasNext()) {
            if (iterator3.next().equals(object)) {
                if (iterator3.hasNext()) {
                    return iterator3.next();
                }
                return collection.iterator().next();
            }
        }
        return iterator3.next();
    }
    
    @Override
    public String toString() {
        final StringBuilder stringBuilder2 = new StringBuilder();
        stringBuilder2.append(this.owner);
        if (!this.getValues().isEmpty()) {
            stringBuilder2.append('[');
            stringBuilder2.append(this.getValues().entrySet().stream().map(StateHolder.PROPERTY_ENTRY_TO_STRING_FUNCTION).collect(Collectors.joining(",")));
            stringBuilder2.append(']');
        }
        return stringBuilder2.toString();
    }
    
    public Collection<Property<?>> getProperties() {
        return Collections.<Property<?>>unmodifiableCollection(this.values.keySet());
    }
    
    public <T extends Comparable<T>> boolean hasProperty(final Property<T> cgl) {
        return this.values.containsKey(cgl);
    }
    
    public <T extends Comparable<T>> T getValue(final Property<T> cgl) {
        final Comparable<?> comparable3 = this.values.get(cgl);
        if (comparable3 == null) {
            throw new IllegalArgumentException("Cannot get property " + cgl + " as it does not exist in " + this.owner);
        }
        return cgl.getValueClass().cast(comparable3);
    }
    
    public <T extends Comparable<T>> Optional<T> getOptionalValue(final Property<T> cgl) {
        final Comparable<?> comparable3 = this.values.get(cgl);
        if (comparable3 == null) {
            return Optional.<T>empty();
        }
        return Optional.<T>of(cgl.getValueClass().cast(comparable3));
    }
    
    public <T extends Comparable<T>, V extends T> S setValue(final Property<T> cgl, final V comparable) {
        final Comparable<?> comparable2 = this.values.get(cgl);
        if (comparable2 == null) {
            throw new IllegalArgumentException("Cannot set property " + cgl + " as it does not exist in " + this.owner);
        }
        if (comparable2 == comparable) {
            return (S)this;
        }
        final S object5 = this.neighbours.get(cgl, comparable);
        if (object5 == null) {
            throw new IllegalArgumentException("Cannot set property " + cgl + " to " + comparable + " on " + this.owner + ", it is not an allowed value");
        }
        return object5;
    }
    
    public void populateNeighbours(final Map<Map<Property<?>, Comparable<?>>, S> map) {
        if (this.neighbours != null) {
            throw new IllegalStateException();
        }
        final Table<Property<?>, Comparable<?>, S> table3 = HashBasedTable.create();
        for (final Map.Entry<Property<?>, Comparable<?>> entry5 : this.values.entrySet()) {
            final Property<?> cgl6 = entry5.getKey();
            for (final Comparable<?> comparable8 : cgl6.getPossibleValues()) {
                if (comparable8 != entry5.getValue()) {
                    table3.put(cgl6, comparable8, map.get(this.makeNeighbourValues(cgl6, comparable8)));
                }
            }
        }
        this.neighbours = (table3.isEmpty() ? table3 : ArrayTable.<Property<?>, Comparable<?>, S>create(table3));
    }
    
    private Map<Property<?>, Comparable<?>> makeNeighbourValues(final Property<?> cgl, final Comparable<?> comparable) {
        final Map<Property<?>, Comparable<?>> map4 = Maps.newHashMap(this.values);
        map4.put(cgl, comparable);
        return map4;
    }
    
    public ImmutableMap<Property<?>, Comparable<?>> getValues() {
        return this.values;
    }
    
    protected static <O, S extends StateHolder<O, S>> Codec<S> codec(final Codec<O> codec, final Function<O, S> function) {
        final S cfl2;
        return (Codec<S>)codec.dispatch("Name", cfl -> cfl.owner, object -> {
            cfl2 = (S)function.apply(object);
            if (((StateHolder)cfl2).getValues().isEmpty()) {
                return Codec.unit(cfl2);
            }
            else {
                return ((StateHolder)cfl2).propertiesCodec.fieldOf("Properties").codec();
            }
        });
    }
    
    static {
        PROPERTY_ENTRY_TO_STRING_FUNCTION = new Function<Map.Entry<Property<?>, Comparable<?>>, String>() {
            @Override
            public String apply(@Nullable final Map.Entry<Property<?>, Comparable<?>> entry) {
                if (entry == null) {
                    return "<NULL>";
                }
                final Property<?> cgl3 = entry.getKey();
                return cgl3.getName() + "=" + this.getName(cgl3, entry.getValue());
            }
            
            private <T extends Comparable<T>> String getName(final Property<T> cgl, final Comparable<?> comparable) {
                return cgl.getName((T)comparable);
            }
        };
    }
}
