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

package com.hypixel.hytale.server.spawning.local;

import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.protocol.BlockMaterial;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.function.function.TriIntObjectDoubleToByteFunction;
import com.hypixel.hytale.server.spawning.util.LightRangePredicate;
import it.unimi.dsi.fastutil.Pair;
import com.hypixel.hytale.math.vector.Vector3d;
import it.unimi.dsi.fastutil.objects.ObjectList;
import com.hypixel.hytale.server.core.universe.world.World;
import it.unimi.dsi.fastutil.objects.Object2ByteMap;
import com.hypixel.hytale.server.spawning.assets.spawns.LightType;
import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
import com.hypixel.hytale.server.core.modules.time.WorldTimeResource;
import java.util.List;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.server.spawning.assets.spawns.config.BeaconNPCSpawn;
import com.hypixel.hytale.server.spawning.wrappers.BeaconSpawnWrapper;
import com.hypixel.hytale.component.ComponentAccessor;
import java.util.logging.Level;
import com.hypixel.hytale.server.spawning.SpawningPlugin;
import com.hypixel.hytale.component.query.Query;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.server.spawning.beacons.LegacySpawnBeaconEntity;
import com.hypixel.hytale.builtin.weather.components.WeatherTracker;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.system.tick.TickingSystem;

public class LocalSpawnControllerSystem extends TickingSystem<EntityStore>
{
    public static final double RUN_FREQUENCY_SECONDS = 5.0;
    private static final int LIGHT_LEVEL_EVALUATION_RADIUS = 4;
    private final Archetype<EntityStore> controllerArchetype;
    private final ComponentType<EntityStore, LocalSpawnController> spawnControllerComponentType;
    private final ComponentType<EntityStore, TransformComponent> transformComponentype;
    private final ComponentType<EntityStore, WeatherTracker> weatherTrackerComponentType;
    private final ComponentType<EntityStore, LocalSpawnBeacon> localSpawnBeaconComponentType;
    private final ComponentType<EntityStore, LegacySpawnBeaconEntity> spawnBeaconComponentType;
    private final ResourceType<EntityStore, LocalSpawnState> localSpawnStateResourceType;
    private final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> beaconSpatialComponent;
    static final /* synthetic */ boolean $assertionsDisabled;
    
    public LocalSpawnControllerSystem(final ComponentType<EntityStore, LocalSpawnController> spawnControllerComponentType, final ComponentType<EntityStore, TransformComponent> transformComponentype, final ComponentType<EntityStore, WeatherTracker> weatherTrackerComponentType, final ComponentType<EntityStore, LocalSpawnBeacon> localSpawnBeaconComponentType, final ComponentType<EntityStore, LegacySpawnBeaconEntity> spawnBeaconComponentType, final ResourceType<EntityStore, LocalSpawnState> localSpawnStateResourceType, final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> beaconSpatialComponent) {
        this.spawnControllerComponentType = spawnControllerComponentType;
        this.transformComponentype = transformComponentype;
        this.weatherTrackerComponentType = weatherTrackerComponentType;
        this.localSpawnBeaconComponentType = localSpawnBeaconComponentType;
        this.spawnBeaconComponentType = spawnBeaconComponentType;
        this.localSpawnStateResourceType = localSpawnStateResourceType;
        this.beaconSpatialComponent = beaconSpatialComponent;
        this.controllerArchetype = Archetype.of(spawnControllerComponentType, PlayerRef.getComponentType(), transformComponentype, weatherTrackerComponentType);
    }
    
    @Override
    public void tick(final float dt, final int systemIndex, @Nonnull final Store<EntityStore> store) {
        final LocalSpawnState localSpawnState = store.getResource(this.localSpawnStateResourceType);
        final List<Ref<EntityStore>> controllers = localSpawnState.getLocalControllerList();
        store.forEachChunk(this.controllerArchetype, (archetypeChunk, commandBuffer) -> {
            int index2 = 0;
            while (index2 < archetypeChunk.size()) {
                final LocalSpawnController spawnControllerComponent2 = archetypeChunk.getComponent(index2, this.spawnControllerComponentType);
                if (!LocalSpawnControllerSystem.$assertionsDisabled && spawnControllerComponent2 == null) {
                    throw new AssertionError();
                }
                else {
                    if (spawnControllerComponent2.tickTimeToNextRunSeconds(dt)) {
                        controllers.add(archetypeChunk.getReferenceTo(index2));
                    }
                    ++index2;
                }
            }
            return;
        });
        if (controllers.isEmpty()) {
            return;
        }
        final World world = store.getExternalData().getWorld();
        final List<LegacySpawnBeaconEntity> pendingSpawns = localSpawnState.getLocalPendingSpawns();
        final ObjectList<Ref<EntityStore>> existingBeacons = SpatialResource.getThreadLocalReferenceList();
        for (int index = 0; index < controllers.size(); ++index) {
            final Ref<EntityStore> reference = controllers.get(index);
            final LocalSpawnController spawnControllerComponent = store.getComponent(reference, this.spawnControllerComponentType);
            assert spawnControllerComponent != null;
            final PlayerRef playerRefComponent = store.getComponent(reference, PlayerRef.getComponentType());
            assert playerRefComponent != null;
            SpawningPlugin.get().getLogger().at(Level.FINE).log("Running local spawn controller for player %s", playerRefComponent.getUsername());
            final TransformComponent transformComponent = store.getComponent(reference, this.transformComponentype);
            assert transformComponent != null;
            final WeatherTracker weatherTrackerComponent = store.getComponent(reference, this.weatherTrackerComponentType);
            assert weatherTrackerComponent != null;
            weatherTrackerComponent.updateEnvironment(transformComponent, store);
            final int environmentIndex = weatherTrackerComponent.getEnvironmentId();
            final List<BeaconSpawnWrapper> possibleBeacons = SpawningPlugin.get().getBeaconSpawnsForEnvironment(environmentIndex);
            if (possibleBeacons == null || possibleBeacons.isEmpty()) {
                spawnControllerComponent.setTimeToNextRunSeconds(5.0);
            }
            else {
                final BeaconSpawnWrapper firstBeacon = possibleBeacons.getFirst();
                double largestDistance = firstBeacon.getBeaconRadius();
                final int[] firstRange = firstBeacon.getSpawn().getYRange();
                int lowestY = firstRange[0];
                int highestY = firstRange[1];
                for (int i = 1; i < possibleBeacons.size(); ++i) {
                    final BeaconSpawnWrapper beacon = possibleBeacons.get(i);
                    final double radius = beacon.getBeaconRadius();
                    if (radius > largestDistance) {
                        largestDistance = radius;
                    }
                    final int[] yRange = beacon.getSpawn().getYRange();
                    if (yRange[0] < lowestY) {
                        lowestY = yRange[0];
                    }
                    if (yRange[1] > highestY) {
                        highestY = yRange[1];
                    }
                }
                largestDistance *= 2.0;
                final Vector3d position = transformComponent.getPosition();
                final double largestDistanceSquared = largestDistance * largestDistance;
                final int yDistance = Math.abs(lowestY) + Math.abs(highestY);
                final int y = MathUtil.floor(position.getY());
                final int minY = Math.max(0, y - yDistance);
                final int maxY = Math.min(319, y + yDistance);
                final SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = store.getResource(this.beaconSpatialComponent);
                spatialResource.getSpatialStructure().ordered(position, largestDistance, existingBeacons);
                final WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType());
                final double sunlightFactor = worldTimeResource.getSunlightFactor();
                final int xPos = MathUtil.floor(position.getX());
                final int yPos = MathUtil.floor(position.getY());
                final int zPos = MathUtil.floor(position.getZ());
                final Object2ByteOpenHashMap<LightType> averageLightValues = new Object2ByteOpenHashMap<LightType>();
                averageLightValues.defaultReturnValue((byte)(-1));
            Label_1130:
                for (int j = 0; j < possibleBeacons.size(); ++j) {
                    final BeaconSpawnWrapper possibleBeacon = possibleBeacons.get(j);
                    if (possibleBeacon.spawnParametersMatch(store)) {
                        for (int k = 0; k < existingBeacons.size(); ++k) {
                            final Ref<EntityStore> existingBeaconReference = existingBeacons.get(k);
                            final LegacySpawnBeaconEntity existingBeaconComponent = store.getComponent(existingBeaconReference, this.spawnBeaconComponentType);
                            assert existingBeaconComponent != null;
                            final TransformComponent existingBeaconTransformComponent = store.getComponent(existingBeaconReference, this.transformComponentype);
                            assert existingBeaconTransformComponent != null;
                            final double existingY = existingBeaconTransformComponent.getPosition().getY();
                            if (existingY <= maxY) {
                                if (existingY >= minY) {
                                    final int existingBeaconIndex = existingBeaconComponent.getSpawnWrapper().getSpawnIndex();
                                    if (existingBeaconIndex == possibleBeacon.getSpawnIndex()) {
                                        continue Label_1130;
                                    }
                                }
                            }
                        }
                        for (int k = 0; k < pendingSpawns.size(); ++k) {
                            final LegacySpawnBeaconEntity pending = pendingSpawns.get(k);
                            final Ref<EntityStore> pendingReference = pending.getReference();
                            final TransformComponent pendingTransformComponent = store.getComponent(pendingReference, TransformComponent.getComponentType());
                            assert pendingTransformComponent != null;
                            final Vector3d pendingPosition = pendingTransformComponent.getPosition();
                            final double pendingY = pendingPosition.getY();
                            if (pendingY <= maxY) {
                                if (pendingY >= minY) {
                                    final double xDiff = position.x - pendingPosition.x;
                                    final double zDiff = position.z - pendingPosition.z;
                                    final double distSquared = xDiff * xDiff + zDiff * zDiff;
                                    if (distSquared <= largestDistanceSquared) {
                                        final int existingBeaconIndex2 = pending.getSpawnWrapper().getSpawnIndex();
                                        if (existingBeaconIndex2 == possibleBeacon.getSpawnIndex()) {
                                            continue Label_1130;
                                        }
                                    }
                                }
                            }
                        }
                        if (spawnLightLevelMatches(world, xPos, yPos, zPos, sunlightFactor, possibleBeacon, averageLightValues)) {
                            final Pair<Ref<EntityStore>, LegacySpawnBeaconEntity> beaconEntityPair = LegacySpawnBeaconEntity.create(possibleBeacon, transformComponent.getPosition(), transformComponent.getRotation(), store);
                            final Ref<EntityStore> beaconRef = beaconEntityPair.first();
                            if (beaconRef != null) {
                                if (beaconRef.isValid()) {
                                    store.ensureComponent(beaconRef, this.localSpawnBeaconComponentType);
                                    SpawningPlugin.get().getLogger().at(Level.FINE).log("Placed spawn beacon of type %s at position %s for player %s", possibleBeacon.getSpawn().getId(), position, playerRefComponent.getUsername());
                                    pendingSpawns.add(beaconEntityPair.second());
                                }
                            }
                        }
                    }
                }
                existingBeacons.clear();
                averageLightValues.clear();
                spawnControllerComponent.setTimeToNextRunSeconds(5.0);
            }
        }
        controllers.clear();
        pendingSpawns.clear();
    }
    
    private static boolean spawnLightLevelMatches(@Nonnull final World world, final int x, final int y, final int z, final double sunlightFactor, @Nonnull final BeaconSpawnWrapper wrapper, @Nonnull final Object2ByteMap<LightType> averageValues) {
        final LightRangePredicate lightRangePredicate = wrapper.getLightRangePredicate();
        if (lightRangePredicate.isTestLightValue()) {
            final byte lightValue = getCachedAverageLightValue(LightType.Light, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> LightRangePredicate.calculateLightValue(_chunk, _x, _y, _z, _sunlightFactor), averageValues);
            if (!lightRangePredicate.testLight(lightValue)) {
                return false;
            }
        }
        if (lightRangePredicate.isTestSkyLightValue()) {
            final byte lightValue = getCachedAverageLightValue(LightType.SkyLight, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> _chunk.getSkyLight(_x, _y, _z), averageValues);
            if (!lightRangePredicate.testSkyLight(lightValue)) {
                return false;
            }
        }
        if (lightRangePredicate.isTestSunlightValue()) {
            final byte lightValue = getCachedAverageLightValue(LightType.Sunlight, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> (byte)(_chunk.getSkyLight(_x, _y, _z) * _sunlightFactor), averageValues);
            if (!lightRangePredicate.testSunlight(lightValue)) {
                return false;
            }
        }
        if (lightRangePredicate.isTestRedLightValue()) {
            final byte lightValue = getCachedAverageLightValue(LightType.RedLight, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> _chunk.getRedBlockLight(_x, _y, _z), averageValues);
            if (!lightRangePredicate.testRedLight(lightValue)) {
                return false;
            }
        }
        if (lightRangePredicate.isTestGreenLightValue()) {
            final byte lightValue = getCachedAverageLightValue(LightType.GreenLight, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> _chunk.getGreenBlockLight(_x, _y, _z), averageValues);
            if (!lightRangePredicate.testGreenLight(lightValue)) {
                return false;
            }
        }
        if (lightRangePredicate.isTestBlueLightValue()) {
            final byte lightValue = getCachedAverageLightValue(LightType.BlueLight, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> _chunk.getBlueBlockLight(_x, _y, _z), averageValues);
            return lightRangePredicate.testBlueLight(lightValue);
        }
        return true;
    }
    
    private static byte getCachedAverageLightValue(final LightType lightType, @Nonnull final World world, final int x, final int y, final int z, final double sunlightFactor, @Nonnull final TriIntObjectDoubleToByteFunction<BlockChunk> valueCalculator, @Nonnull final Object2ByteMap<LightType> averageValues) {
        byte cachedValue = averageValues.getByte(lightType);
        if (cachedValue < 0) {
            int counted = 0;
            int total = 0;
            for (int xOffset = x - 4; xOffset < x + 4; ++xOffset) {
                for (int zOffset = z - 4; zOffset < z + 4; ++zOffset) {
                    final WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(xOffset, zOffset));
                    if (chunk != null) {
                        final BlockChunk blockChunk = chunk.getBlockChunk();
                        for (int yOffset = y; yOffset < y + 4; ++yOffset) {
                            final int blockId = chunk.getBlock(xOffset, yOffset, zOffset);
                            if (blockId == 0 || BlockType.getAssetMap().getAsset(blockId).getMaterial() != BlockMaterial.Solid) {
                                ++counted;
                                total += valueCalculator.apply(xOffset, yOffset, zOffset, blockChunk, sunlightFactor);
                            }
                        }
                    }
                }
            }
            cachedValue = (byte)((counted > 0) ? ((byte)(total / (float)counted)) : 0);
            averageValues.put(lightType, cachedValue);
        }
        return cachedValue;
    }
}
