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

package com.hypixel.hytale.builtin.hytalegenerator.props.prefab;

import com.hypixel.hytale.server.core.prefab.PrefabWeights;
import org.checkerframework.checker.nullness.compatqual.NonNullDecl;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.builtin.hytalegenerator.material.FluidMaterial;
import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.common.util.ExceptionUtil;
import com.hypixel.hytale.builtin.hytalegenerator.props.entity.EntityPlacementData;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.IPrefabBuffer;
import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.ArrayVoxelSpace;
import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferCall;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.views.EntityContainer;
import java.util.Random;
import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.RotatedPositionsScanResult;
import com.hypixel.hytale.builtin.hytalegenerator.props.ScanResult;
import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer;
import com.hypixel.hytale.builtin.hytalegenerator.material.Material;
import com.hypixel.hytale.builtin.hytalegenerator.datastructures.voxelspace.VoxelSpace;
import com.hypixel.hytale.server.core.prefab.PrefabRotation;
import com.hypixel.hytale.builtin.hytalegenerator.bounds.SpaceSize;
import java.util.Iterator;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator;
import com.hypixel.hytale.builtin.hytalegenerator.scanners.OriginScanner;
import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.StaticDirectionality;
import java.util.ArrayList;
import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox;
import javax.annotation.Nullable;
import javax.annotation.Nonnull;
import com.hypixel.hytale.builtin.hytalegenerator.patterns.Pattern;
import java.util.function.Function;
import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.RotatedPosition;
import com.hypixel.hytale.builtin.hytalegenerator.bounds.Bounds3i;
import com.hypixel.hytale.builtin.hytalegenerator.props.directionality.Directionality;
import com.hypixel.hytale.builtin.hytalegenerator.BlockMask;
import com.hypixel.hytale.builtin.hytalegenerator.framework.math.SeedGenerator;
import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache;
import com.hypixel.hytale.builtin.hytalegenerator.conveyor.stagedconveyor.ContextDependency;
import com.hypixel.hytale.builtin.hytalegenerator.scanners.Scanner;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer;
import java.util.List;
import com.hypixel.hytale.builtin.hytalegenerator.datastructures.WeightedMap;
import com.hypixel.hytale.builtin.hytalegenerator.props.Prop;

public class PrefabProp extends Prop
{
    private final WeightedMap<List<PrefabBuffer>> prefabPool;
    private final Scanner scanner;
    private ContextDependency contextDependency;
    private final MaterialCache materialCache;
    private final SeedGenerator seedGenerator;
    private final BlockMask materialMask;
    private final Directionality directionality;
    private final Bounds3i readBounds_voxelGrid;
    private final Bounds3i writeBounds_voxelGrid;
    private final Bounds3i prefabBounds_voxelGrid;
    private final List<PrefabProp> childProps;
    private final List<RotatedPosition> childPositions;
    private final Function<String, List<PrefabBuffer>> childPrefabLoader;
    private final Scanner moldingScanner;
    private final Pattern moldingPattern;
    private final MoldingDirection moldingDirection;
    private final boolean moldChildren;
    private final int prefabId;
    private boolean loadEntities;
    
    public PrefabProp(@Nonnull final WeightedMap<List<PrefabBuffer>> prefabPool, @Nonnull final Scanner scanner, @Nonnull final Directionality directionality, @Nonnull final MaterialCache materialCache, @Nonnull final BlockMask materialMask, @Nonnull final PrefabMoldingConfiguration prefabMoldingConfiguration, @Nullable final Function<String, List<PrefabBuffer>> childPrefabLoader, @Nonnull final SeedBox seedBox, final boolean loadEntities) {
        this.prefabId = this.hashCode();
        this.prefabPool = prefabPool;
        this.scanner = scanner;
        this.directionality = directionality;
        this.materialCache = materialCache;
        this.seedGenerator = new SeedGenerator(seedBox.createSupplier().get());
        this.materialMask = materialMask;
        this.loadEntities = loadEntities;
        this.childProps = new ArrayList<PrefabProp>();
        this.childPositions = new ArrayList<RotatedPosition>();
        this.childPrefabLoader = (Function<String, List<PrefabBuffer>>)((childPrefabLoader == null) ? (s -> null) : childPrefabLoader);
        this.moldingScanner = prefabMoldingConfiguration.moldingScanner;
        this.moldingPattern = prefabMoldingConfiguration.moldingPattern;
        this.moldingDirection = prefabMoldingConfiguration.moldingDirection;
        this.moldChildren = prefabMoldingConfiguration.moldChildren;
        this.contextDependency = new ContextDependency();
        final Vector3i readRange = directionality.getReadRangeWith(scanner);
        for (final List<PrefabBuffer> prefabList : prefabPool.allElements()) {
            if (prefabList.isEmpty()) {
                throw new IllegalArgumentException("prefab pool contains empty list");
            }
            for (final PrefabBuffer prefab : prefabList) {
                if (prefab == null) {
                    throw new IllegalArgumentException("prefab pool contains list with null element");
                }
                final PrefabBuffer.PrefabBufferAccessor prefabAccess = prefab.newAccess();
                final PrefabBuffer.ChildPrefab[] childPrefabs = prefabAccess.getChildPrefabs();
                int childId = 0;
                for (final PrefabBuffer.ChildPrefab child : childPrefabs) {
                    final RotatedPosition childPosition = new RotatedPosition(child.getX(), child.getY(), child.getZ(), child.getRotation());
                    String childPath = child.getPath().replace('.', '/');
                    childPath = childPath.replace("*", "");
                    final List<PrefabBuffer> childPrefabBuffers = this.childPrefabLoader.apply(childPath);
                    final WeightedMap<List<PrefabBuffer>> weightedChildPrefabs = new WeightedMap<List<PrefabBuffer>>();
                    weightedChildPrefabs.add(childPrefabBuffers, 1.0);
                    final StaticDirectionality childDirectionality = new StaticDirectionality(child.getRotation(), Pattern.yesPattern());
                    final PrefabProp childProp = new PrefabProp(weightedChildPrefabs, OriginScanner.getInstance(), childDirectionality, materialCache, materialMask, this.moldChildren ? prefabMoldingConfiguration : PrefabMoldingConfiguration.none(), childPrefabLoader, seedBox.child(String.valueOf(childId++)), loadEntities);
                    this.childProps.add(childProp);
                    this.childPositions.add(childPosition);
                }
                final Vector3i writeRange = this.getWriteRange(prefabAccess);
                for (int i = 0; i < this.childPositions.size(); ++i) {
                    final PrefabProp child2 = this.childProps.get(i);
                    final Vector3i position = this.childPositions.get(i).toVector3i();
                    final Vector3i childWriteRange = child2.getContextDependency().getWriteRange();
                    int maxRange = Calculator.max(position.x, position.y, position.z);
                    maxRange += Calculator.max(childWriteRange.x, childWriteRange.y, childWriteRange.z);
                    writeRange.x = Math.max(writeRange.x, maxRange);
                    writeRange.y = Math.max(writeRange.y, maxRange);
                    writeRange.z = Math.max(writeRange.z, maxRange);
                }
                final ContextDependency contextDependency = new ContextDependency(readRange, writeRange);
                this.contextDependency = ContextDependency.mostOf(this.contextDependency, contextDependency);
                prefabAccess.release();
            }
        }
        this.readBounds_voxelGrid = this.contextDependency.getReadBounds_voxelGrid();
        this.writeBounds_voxelGrid = this.contextDependency.getWriteBounds_voxelGrid();
        this.prefabBounds_voxelGrid = new Bounds3i();
        this.prefabBounds_voxelGrid.min.assign(this.contextDependency.getWriteRange()).scale(-1);
        this.prefabBounds_voxelGrid.max.assign(this.contextDependency.getWriteRange()).add(Vector3i.ALL_ONES);
    }
    
    private Vector3i getWriteRange(final PrefabBuffer.PrefabBufferAccessor prefabAccess) {
        SpaceSize space = new SpaceSize();
        for (final PrefabRotation rotation : this.directionality.getPossibleRotations()) {
            final Vector3i max = PropPrefabUtil.getMax(prefabAccess, rotation);
            max.add(1, 1, 1);
            final Vector3i min = PropPrefabUtil.getMin(prefabAccess, rotation);
            space = SpaceSize.merge(space, new SpaceSize(min, max));
        }
        space = SpaceSize.stack(space, this.scanner.readSpaceWith(this.directionality.getGeneralPattern()));
        return space.getRange();
    }
    
    @Override
    public ScanResult scan(@Nonnull final Vector3i position, @Nonnull final VoxelSpace<Material> materialSpace, @Nonnull final WorkerIndexer.Id id) {
        final Scanner.Context scannerContext = new Scanner.Context(position, this.directionality.getGeneralPattern(), materialSpace, id);
        final List<Vector3i> validPositions = this.scanner.scan(scannerContext);
        final Vector3i patternPosition = new Vector3i();
        final Pattern.Context patternContext = new Pattern.Context(patternPosition, materialSpace, id);
        final RotatedPositionsScanResult scanResult = new RotatedPositionsScanResult(new ArrayList<RotatedPosition>());
        for (final Vector3i validPosition : validPositions) {
            patternPosition.assign(validPosition);
            final PrefabRotation rotation = this.directionality.getRotationAt(patternContext);
            if (rotation == null) {
                continue;
            }
            scanResult.positions.add(new RotatedPosition(validPosition.x, validPosition.y, validPosition.z, rotation));
        }
        return scanResult;
    }
    
    @Override
    public void place(@Nonnull final Context context) {
        if (this.prefabPool.size() == 0) {
            return;
        }
        final List<RotatedPosition> positions = RotatedPositionsScanResult.cast(context.scanResult).positions;
        if (positions == null) {
            return;
        }
        final Bounds3i writeSpaceBounds_voxelGrid = context.materialSpace.getBounds();
        for (final RotatedPosition position : positions) {
            final Bounds3i localPrefabWriteBounds_voxelGrid = this.prefabBounds_voxelGrid.clone().offset(position.toVector3i());
            if (!localPrefabWriteBounds_voxelGrid.intersects(writeSpaceBounds_voxelGrid)) {
                continue;
            }
            this.place(position, context.materialSpace, context.entityBuffer, context.workerId);
        }
    }
    
    private PrefabBuffer pickPrefab(final Random rand) {
        final List<PrefabBuffer> list = this.prefabPool.pick(rand);
        final int randomIndex = rand.nextInt(list.size());
        return list.get(randomIndex);
    }
    
    private void place(final RotatedPosition position, @Nonnull final VoxelSpace<Material> materialSpace, @Nonnull final EntityContainer entityBuffer, @Nonnull final WorkerIndexer.Id id) {
        final Random random = new Random(this.seedGenerator.seedAt(position.x, position.y, position.z));
        final PrefabBufferCall callInstance = new PrefabBufferCall(random, position.rotation);
        final PrefabBuffer prefab = this.pickPrefab(random);
        final PrefabBuffer.PrefabBufferAccessor prefabAccess = prefab.newAccess();
        VoxelSpace<Integer> moldingOffsets = null;
        if (this.moldingDirection != MoldingDirection.NONE) {
            final int prefabMinX = prefabAccess.getMinX(position.rotation);
            final int prefabMinZ = prefabAccess.getMinZ(position.rotation);
            final int prefabMaxX = prefabAccess.getMaxX(position.rotation);
            final int prefabMaxZ = prefabAccess.getMaxZ(position.rotation);
            final int prefabSizeX = prefabMaxX - prefabMinX;
            final int prefabSizeZ = prefabMaxZ - prefabMinZ;
            moldingOffsets = new ArrayVoxelSpace<Integer>(prefabSizeX, 1, prefabSizeZ);
            moldingOffsets.setOrigin(-position.x - prefabMinX, 0, -position.z - prefabMinZ);
            if (this.moldingDirection == MoldingDirection.DOWN || this.moldingDirection == MoldingDirection.UP) {
                final Vector3i pointer = new Vector3i(0, position.y, 0);
                final Scanner.Context scannerContext = new Scanner.Context(pointer, this.moldingPattern, materialSpace, id);
                pointer.x = moldingOffsets.minX();
                while (pointer.x < moldingOffsets.maxX()) {
                    pointer.z = moldingOffsets.minZ();
                    while (pointer.z < moldingOffsets.maxZ()) {
                        final List<Vector3i> scanResult = this.moldingScanner.scan(scannerContext);
                        Integer offset = scanResult.isEmpty() ? null : Integer.valueOf(scanResult.getFirst().y - position.y);
                        if (offset != null && this.moldingDirection == MoldingDirection.UP) {
                            --offset;
                        }
                        moldingOffsets.set(offset, pointer.x, 0, pointer.z);
                        final Vector3i vector3i = pointer;
                        ++vector3i.z;
                    }
                    final Vector3i vector3i2 = pointer;
                    ++vector3i2.x;
                }
            }
        }
        try {
            final Vector3i prefabPositionVector = position.toVector3i();
            final VoxelSpace<Integer> moldingOffsetsFinal = moldingOffsets;
            final int y;
            prefabAccess.forEach(IPrefabBuffer.iterateAllColumns(), (x, y, z, blockId, holder, support, rotation, filler, call, fluidId, fluidLevel) -> {
                final int worldX = position.x + x;
                int worldY = position.y + y;
                final int worldZ = position.z + z;
                if (!materialSpace.isInsideSpace(worldX, worldY, worldZ)) {
                    return;
                }
                else {
                    final SolidMaterial solid = this.materialCache.getSolidMaterial(blockId, support, rotation, filler, (holder != null) ? holder.clone() : null);
                    final FluidMaterial fluid = this.materialCache.getFluidMaterial(fluidId, (byte)fluidLevel);
                    final Material material = this.materialCache.getMaterial(solid, fluid);
                    final int materialHash = material.hashMaterialIds();
                    if (!this.materialMask.canPlace(materialHash)) {
                        return;
                    }
                    else {
                        if (this.moldingDirection == MoldingDirection.DOWN || this.moldingDirection == MoldingDirection.UP) {
                            Integer offset3 = null;
                            if (moldingOffsetsFinal.isInsideSpace(worldX, 0, worldZ)) {
                                offset3 = moldingOffsetsFinal.getContent(worldX, 0, worldZ);
                            }
                            if (offset3 == null) {
                                return;
                            }
                            else {
                                worldY += offset3;
                            }
                        }
                        final Material worldMaterial = materialSpace.getContent(worldX, worldY, worldZ);
                        final int worldMaterialHash = worldMaterial.hashMaterialIds();
                        if (!this.materialMask.canReplace(materialHash, worldMaterialHash)) {
                            return;
                        }
                        else {
                            materialSpace.set(material, worldX, worldY, worldZ);
                            return;
                        }
                    }
                }
            }, (cx, cz, entityWrappers, buffer) -> {
                if (!this.loadEntities) {
                    return;
                }
                else if (entityWrappers == null) {
                    return;
                }
                else {
                    for (int j = 0; j < entityWrappers.length; ++j) {
                        final TransformComponent transformComp = entityWrappers[j].getComponent(TransformComponent.getComponentType());
                        if (transformComp != null) {
                            final Vector3d entityPosition = transformComp.getPosition().clone();
                            buffer.rotation.rotate(entityPosition);
                            final Vector3d entityWorldPosition = entityPosition.add(prefabPositionVector);
                            if (!(!entityBuffer.isInsideBuffer((int)entityWorldPosition.x, (int)entityWorldPosition.y, (int)entityWorldPosition.z))) {
                                final Holder<EntityStore> entityClone = entityWrappers[j].clone();
                                final TransformComponent transformComp2 = entityClone.getComponent(TransformComponent.getComponentType());
                                if (transformComp2 != null) {
                                    final Vector3d entityPosition2 = transformComp2.getPosition();
                                    entityPosition2.x = entityWorldPosition.x;
                                    entityPosition2.y = entityWorldPosition.y;
                                    entityPosition2.z = entityWorldPosition.z;
                                    if (!materialSpace.isInsideSpace((int)Math.floor(entityPosition2.x), (int)Math.floor(entityPosition2.y), (int)Math.floor(entityPosition2.z))) {
                                        return;
                                    }
                                    else {
                                        new EntityPlacementData(new Vector3i(), PrefabRotation.ROTATION_0, entityClone, this.prefabId);
                                        final EntityPlacementData entityPlacementData;
                                        final EntityPlacementData placementData = entityPlacementData;
                                        entityBuffer.addEntity(placementData);
                                    }
                                }
                            }
                        }
                    }
                    return;
                }
            }, (x, y, z, path, fitHeightmap, inheritSeed, inheritHeightCondition, weights, rotation, t) -> {}, callInstance);
        }
        catch (final Exception e) {
            String msg = "Couldn't place prefab prop.";
            msg = msg;
            msg += ExceptionUtil.toStringWithStack(e);
            HytaleLogger.getLogger().atWarning().log(msg);
        }
        finally {
            prefabAccess.release();
        }
        for (int i = 0; i < this.childProps.size(); ++i) {
            final PrefabProp prop = this.childProps.get(i);
            RotatedPosition childPosition = this.childPositions.get(i).getRelativeTo(position);
            final Vector3i rotatedChildPositionVec = new Vector3i(childPosition.x, childPosition.y, childPosition.z);
            position.rotation.rotate(rotatedChildPositionVec);
            if (moldingOffsets != null && moldingOffsets.isInsideSpace(childPosition.x, 0, childPosition.z)) {
                final Integer offset2 = moldingOffsets.getContent(childPosition.x, 0, childPosition.z);
                if (offset2 == null) {
                    continue;
                }
                final int y = childPosition.y + offset2;
                childPosition = new RotatedPosition(childPosition.x, y, childPosition.z, childPosition.rotation);
            }
            prop.place(childPosition, materialSpace, entityBuffer, id);
        }
    }
    
    @Override
    public ContextDependency getContextDependency() {
        return this.contextDependency;
    }
    
    @NonNullDecl
    @Override
    public Bounds3i getReadBounds_voxelGrid() {
        return this.readBounds_voxelGrid;
    }
    
    @Nonnull
    @Override
    public Bounds3i getWriteBounds_voxelGrid() {
        return this.writeBounds_voxelGrid;
    }
}
