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

package com.hypixel.hytale.builtin.blockphysics;

import com.hypixel.hytale.event.IEvent;
import javax.annotation.Nullable;
import com.hypixel.hytale.event.IEventDispatcher;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.data.unknown.UnknownComponents;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.util.stream.Stream;
import java.io.IOException;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.function.Predicate;
import java.util.Objects;
import com.hypixel.hytale.common.util.ExceptionUtil;
import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferUtil;
import com.hypixel.hytale.server.core.util.io.FileUtil;
import java.util.ArrayList;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.Set;
import com.hypixel.hytale.server.core.prefab.PrefabStore;
import java.util.Collection;
import java.util.EnumSet;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.universe.world.ValidationOption;
import java.util.List;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer;
import com.hypixel.hytale.server.core.util.FillerBlockUtil;

public class PrefabBufferValidator
{
    private static final FillerBlockUtil.FillerFetcher<IPrefabBuffer, Void> FILLER_FETCHER;
    
    @Nonnull
    public static List<String> validateAllPrefabs(@Nonnull final List<ValidationOption> list) {
        final Set<ValidationOption> options = list.isEmpty() ? EnumSet.of(ValidationOption.BLOCK_STATES, ValidationOption.ENTITIES, ValidationOption.BLOCKS, ValidationOption.BLOCK_FILLER) : EnumSet.copyOf(list);
        final List<String> out = validatePrefabsInPath(PrefabStore.get().getWorldGenPrefabsPath(), options);
        out.addAll(validatePrefabsInPath(PrefabStore.get().getAssetPrefabsPath(), options));
        out.addAll(validatePrefabsInPath(PrefabStore.get().getServerPrefabsPath(), options));
        return out;
    }
    
    @Nonnull
    public static List<String> validatePrefabsInPath(@Nonnull final Path dataFolder, @Nonnull final Set<ValidationOption> options) {
        if (!Files.exists(dataFolder, new LinkOption[0])) {
            return new ArrayList<String>();
        }
        try (final Stream<Path> stream = Files.walk(dataFolder, FileUtil.DEFAULT_WALK_TREE_OPTIONS_ARRAY)) {
            final List<? super Object> list = stream.map(path -> {
                if (!Files.isRegularFile(path, new LinkOption[0]) || !path.toString().endsWith(".prefab.json")) {
                    return null;
                }
                else {
                    try {
                        final IPrefabBuffer prefab = PrefabBufferUtil.getCached(path);
                        try {
                            final String results = validate(prefab, options);
                            return (results != null) ? (String.valueOf(path) + "\n" + results) : null;
                        }
                        finally {
                            prefab.release();
                        }
                    }
                    catch (final Throwable e2) {
                        return String.valueOf(path) + "\n\t" + ExceptionUtil.combineMessages(e2, "\n\t");
                    }
                }
            }).filter(Objects::nonNull).collect((Collector<? super Object, ?, List<? super Object>>)Collectors.toList());
            return (List<String>)list;
        }
        catch (final IOException e) {
            throw SneakyThrow.sneakyThrow(e);
        }
    }
    
    @Nullable
    public static String validate(@Nonnull final IPrefabBuffer prefab, @Nonnull final Set<ValidationOption> options) {
        final ComponentType<EntityStore, UnknownComponents<EntityStore>> unknownComponentType = EntityStore.REGISTRY.getUnknownComponentType();
        final StringBuilder sb = new StringBuilder();
        final int offsetX = prefab.getAnchorX();
        final int offsetY = prefab.getAnchorY();
        final int offsetZ = prefab.getAnchorZ();
        final IPrefabBuffer.RawBlockConsumer<Void> legacyValidator = WorldValidationUtil.blockValidator(offsetX, offsetY, offsetZ, sb, options);
        prefab.forEachRaw(IPrefabBuffer.iterateAllColumns(), (x, y, z, mask, blockId, chance, holder, supportValue, rotation, filler, o) -> {
            legacyValidator.accept(x, y, z, mask, blockId, chance, holder, supportValue, rotation, filler, o);
            final IEventDispatcher<ValidateBlockEvent, ValidateBlockEvent> dispatch = HytaleServer.get().getEventBus().dispatchFor((Class<? super ValidateBlockEvent>)ValidateBlockEvent.class);
            if (dispatch.hasListener()) {
                dispatch.dispatch(new ValidateBlockEvent(x, y, z, blockId, supportValue, rotation, filler, holder, sb));
            }
            if (options.contains(ValidationOption.BLOCK_FILLER)) {
                final FillerBlockUtil.ValidationResult fillerResult = FillerBlockUtil.validateBlock(x, y, z, blockId, rotation, filler, prefab, null, PrefabBufferValidator.FILLER_FETCHER);
                switch (fillerResult) {
                    case INVALID_BLOCK: {
                        final BlockType blockType = BlockType.getAssetMap().getAsset(blockId);
                        sb.append("\tBlock ").append((blockType != null) ? blockType.getId() : "<missing>").append(" at ").append(x).append(", ").append(y).append(", ").append(z).append(" is not valid filler").append('\n');
                        break;
                    }
                    case INVALID_FILLER: {
                        final BlockType blockType2 = BlockType.getAssetMap().getAsset(blockId);
                        sb.append("\tBlock ").append((blockType2 != null) ? blockType2.getId() : "<missing>").append(" at ").append(x).append(", ").append(y).append(", ").append(z).append(" has invalid/missing filler blocks").append('\n');
                        break;
                    }
                }
            }
            return;
        }, (x, y, z, fluidId, level, unused) -> {}, (x, z, holders, o) -> {
            if (holders == null) {
                return;
            }
            else {
                if (options.contains(ValidationOption.ENTITIES)) {
                    for (final Holder<EntityStore> entityHolder : holders) {
                        final UnknownComponents<EntityStore> unknownComponents = entityHolder.getComponent((ComponentType<EntityStore, UnknownComponents<EntityStore>>)unknownComponentType);
                        if (unknownComponents != null && !unknownComponents.getUnknownComponents().isEmpty()) {
                            sb.append("\tUnknown Entity Components: ").append(unknownComponents.getUnknownComponents()).append("\n");
                        }
                    }
                }
                return;
            }
        }, null);
        return sb.isEmpty() ? null : sb.toString();
    }
    
    static {
        FILLER_FETCHER = new FillerBlockUtil.FillerFetcher<IPrefabBuffer, Void>() {
            @Override
            public int getBlock(final IPrefabBuffer iPrefabBuffer, final Void unused, final int x, final int y, final int z) {
                return iPrefabBuffer.getBlockId(x, y, z);
            }
            
            @Override
            public int getFiller(final IPrefabBuffer iPrefabBuffer, final Void unused, final int x, final int y, final int z) {
                return iPrefabBuffer.getFiller(x, y, z);
            }
            
            @Override
            public int getRotationIndex(final IPrefabBuffer iPrefabBuffer, final Void unused, final int x, final int y, final int z) {
                return iPrefabBuffer.getRotationIndex(x, y, z);
            }
        };
    }
    
    record ValidateBlockEvent(int x, int y, int z, int blockId, int support, int rotation, int filler, @Nullable Holder<ChunkStore> holder, StringBuilder reason) implements IEvent<Void> {
        @Nullable
        public Holder<ChunkStore> holder() {
            return this.holder;
        }
    }
}
