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

import java.util.ArrayList;
import javax.annotation.Nullable;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import com.google.common.base.MoreObjects;
import com.mojang.serialization.Codec;
import net.minecraft.util.Codecs;
import java.util.Iterator;
import com.google.common.collect.UnmodifiableIterator;
import java.util.Collection;
import com.google.common.collect.ImmutableMap;
import java.util.List;
import java.util.stream.Stream;
import com.mojang.datafixers.util.Pair;
import java.util.Collections;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.mojang.serialization.MapCodec;
import java.util.function.Supplier;
import com.mojang.serialization.Decoder;
import com.mojang.serialization.Encoder;
import java.util.Map;
import java.util.function.Function;
import com.google.common.collect.ImmutableList;
import net.minecraft.world.level.block.state.properties.Property;
import com.google.common.collect.ImmutableSortedMap;
import java.util.regex.Pattern;

public class StateDefinition<O, S extends StateHolder<O, S>> {
    private static final Pattern NAME_PATTERN;
    private final O owner;
    private final ImmutableSortedMap<String, Property<?>> propertiesByName;
    private final ImmutableList<S> states;
    
    protected StateDefinition(final Function<O, S> function, final O object, final Factory<O, S> b, final Map<String, Property<?>> map) {
        this.owner = object;
        this.propertiesByName = ImmutableSortedMap.<String, Property<?>>copyOf(map);
        final Supplier<S> supplier6 = () -> function.apply(object);
        MapCodec<S> mapCodec7 = (MapCodec<S>)MapCodec.of(Encoder.empty(), Decoder.unit((Supplier)supplier6));
        for (final Map.Entry<String, Property<?>> entry9 : this.propertiesByName.entrySet()) {
            mapCodec7 = StateDefinition.appendPropertyCodec(mapCodec7, supplier6, entry9.getKey(), entry9.getValue());
        }
        final MapCodec<S> mapCodec8 = mapCodec7;
        final Map<Map<Property<?>, Comparable<?>>, S> map2 = Maps.newLinkedHashMap();
        final List<S> list7 = Lists.newArrayList();
        Stream<List<Pair<Property<?>, Comparable<?>>>> stream11 = Stream.<List<Pair<Property<?>, Comparable<?>>>>of(Collections.<Pair<Property<?>, Comparable<?>>>emptyList());
        for (final Property<?> cgl13 : this.propertiesByName.values()) {
            final Property property;
            final List<Pair<Property<?>, Comparable<?>>> list8;
            final Object o;
            stream11 = stream11.<List<Pair<Property<?>, Comparable<?>>>>flatMap(list -> property.getPossibleValues().stream().map(comparable -> {
                list8 = Lists.newArrayList(list);
                list8.add((Pair<Property<?>, Comparable<?>>)Pair.of(o, comparable));
                return list8;
            }));
        }
        final ImmutableMap<Property<?>, Comparable<?>> immutableMap7;
        final com.mojang.serialization.MapCodec<S> mapCodec9;
        final S cfl8;
        final Map<ImmutableMap<Property<?>, Comparable<?>>, S> map3;
        final List<S> list9;
        stream11.forEach(list6 -> {
            immutableMap7 = list6.stream().collect(ImmutableMap.toImmutableMap(Pair::getFirst, Pair::getSecond));
            cfl8 = b.create(object, immutableMap7, mapCodec9);
            map3.put(immutableMap7, cfl8);
            list9.add(cfl8);
            return;
        });
        for (final S cfl9 : list7) {
            ((StateHolder<O, S>)cfl9).populateNeighbours(map2);
        }
        this.states = ImmutableList.<S>copyOf(list7);
    }
    
    private static <S extends StateHolder<?, S>, T extends Comparable<T>> MapCodec<S> appendPropertyCodec(final MapCodec<S> mapCodec, final Supplier<S> supplier, final String string, final Property<T> cgl) {
        return (MapCodec<S>)Codec.mapPair((MapCodec)mapCodec, (MapCodec)Codecs.<Property.Value<T>>setPartial((com.mojang.serialization.MapCodec<Property.Value<T>>)cgl.valueCodec().fieldOf(string), () -> cgl.value(supplier.get()))).xmap(pair -> ((StateHolder)pair.getFirst()).<T, Comparable>setValue(cgl, ((Property.Value)pair.getSecond()).value()), cfl -> Pair.of(cfl, cgl.value(cfl)));
    }
    
    public ImmutableList<S> getPossibleStates() {
        return this.states;
    }
    
    public S any() {
        return this.states.get(0);
    }
    
    public O getOwner() {
        return this.owner;
    }
    
    public Collection<Property<?>> getProperties() {
        return this.propertiesByName.values();
    }
    
    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).add("block", this.owner).add("properties", this.propertiesByName.values().stream().map(Property::getName).collect(Collectors.toList())).toString();
    }
    
    @Nullable
    public Property<?> getProperty(final String string) {
        return this.propertiesByName.get(string);
    }
    
    static {
        NAME_PATTERN = Pattern.compile("^[a-z0-9_]+$");
    }
    
    public static class Builder<O, S extends StateHolder<O, S>> {
        private final O owner;
        private final Map<String, Property<?>> properties;
        
        public Builder(final O object) {
            this.properties = Maps.newHashMap();
            this.owner = object;
        }
        
        public Builder<O, S> add(final Property<?>... arr) {
            for (final Property<?> cgl6 : arr) {
                this.validateProperty(cgl6);
                this.properties.put(cgl6.getName(), cgl6);
            }
            return this;
        }
        
        private <T extends Comparable<T>> void validateProperty(final Property<T> cgl) {
            final String string3 = cgl.getName();
            if (!StateDefinition.NAME_PATTERN.matcher(string3).matches()) {
                throw new IllegalArgumentException(this.owner + " has invalidly named property: " + string3);
            }
            final Collection<T> collection4 = cgl.getPossibleValues();
            if (collection4.size() <= 1) {
                throw new IllegalArgumentException(this.owner + " attempted use property " + string3 + " with <= 1 possible values");
            }
            for (final T comparable6 : collection4) {
                final String string4 = cgl.getName(comparable6);
                if (!StateDefinition.NAME_PATTERN.matcher(string4).matches()) {
                    throw new IllegalArgumentException(this.owner + " has property: " + string3 + " with invalidly named value: " + string4);
                }
            }
            if (this.properties.containsKey(string3)) {
                throw new IllegalArgumentException(this.owner + " has duplicate property: " + string3);
            }
        }
        
        public StateDefinition<O, S> create(final Function<O, S> function, final Factory<O, S> b) {
            return new StateDefinition<O, S>(function, this.owner, b, this.properties);
        }
    }
    
    public interface Factory<O, S> {
        S create(final O object, final ImmutableMap<Property<?>, Comparable<?>> immutableMap, final MapCodec<S> mapCodec);
    }
}
