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

package com.hypixel.hytale.server.core.universe.world.lighting;

import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import java.util.logging.Level;
import java.util.concurrent.ConcurrentHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
import com.hypixel.hytale.math.vector.Vector3i;
import java.util.Set;
import java.util.concurrent.Semaphore;
import com.hypixel.hytale.server.core.universe.world.World;
import javax.annotation.Nonnull;
import com.hypixel.hytale.logger.HytaleLogger;

public class ChunkLightingManager implements Runnable
{
    @Nonnull
    private final HytaleLogger logger;
    @Nonnull
    private final Thread thread;
    @Nonnull
    private final World world;
    private final Semaphore semaphore;
    private final Set<Vector3i> set;
    private final ObjectArrayFIFOQueue<Vector3i> queue;
    private LightCalculation lightCalculation;
    
    public ChunkLightingManager(@Nonnull final World world) {
        this.semaphore = new Semaphore(1);
        this.set = (Set<Vector3i>)ConcurrentHashMap.newKeySet();
        this.queue = new ObjectArrayFIFOQueue<Vector3i>();
        this.logger = HytaleLogger.get("World|" + world.getName() + "|L");
        (this.thread = new Thread((Runnable)this, "ChunkLighting - " + world.getName())).setDaemon(true);
        this.world = world;
        this.lightCalculation = new FloodLightCalculation(this);
    }
    
    @Nonnull
    protected HytaleLogger getLogger() {
        return this.logger;
    }
    
    @Nonnull
    public World getWorld() {
        return this.world;
    }
    
    public void setLightCalculation(final LightCalculation lightCalculation) {
        this.lightCalculation = lightCalculation;
    }
    
    public LightCalculation getLightCalculation() {
        return this.lightCalculation;
    }
    
    public void start() {
        this.thread.start();
    }
    
    @Override
    public void run() {
        try {
            int lastSize = 0;
            int count = 0;
            while (!this.thread.isInterrupted()) {
                this.semaphore.drainPermits();
                final Vector3i pos;
                synchronized (this.queue) {
                    pos = (this.queue.isEmpty() ? null : this.queue.dequeue());
                }
                if (pos != null) {
                    this.process(pos);
                }
                Thread.yield();
                final int currentSize;
                synchronized (this.queue) {
                    currentSize = this.queue.size();
                }
                if (currentSize != lastSize) {
                    count = 0;
                    lastSize = currentSize;
                }
                else if (count <= currentSize) {
                    ++count;
                }
                else {
                    this.semaphore.acquire();
                }
            }
        }
        catch (final InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }
    }
    
    private void process(final Vector3i chunkPosition) {
        try {
            switch (this.lightCalculation.calculateLight(chunkPosition)) {
                case NOT_LOADED:
                case WAITING_FOR_NEIGHBOUR:
                case DONE: {
                    this.set.remove(chunkPosition);
                    break;
                }
                case INVALIDATED: {
                    synchronized (this.queue) {
                        this.queue.enqueue(chunkPosition);
                    }
                    break;
                }
            }
        }
        catch (final Exception e) {
            this.logger.at(Level.WARNING).withCause(e).log("Failed to calculate lighting for: %s", chunkPosition);
            this.set.remove(chunkPosition);
        }
    }
    
    public boolean interrupt() {
        if (this.thread.isAlive()) {
            this.thread.interrupt();
            return true;
        }
        return false;
    }
    
    public void stop() {
        try {
            int i = 0;
            while (this.thread.isAlive()) {
                this.thread.interrupt();
                this.thread.join(this.world.getTickStepNanos() / 1000000);
                i += this.world.getTickStepNanos() / 1000000;
                if (i > 5000) {
                    final StringBuilder sb = new StringBuilder();
                    for (final StackTraceElement traceElement : this.thread.getStackTrace()) {
                        sb.append("\tat ").append(traceElement).append('\n');
                    }
                    HytaleLogger.getLogger().at(Level.SEVERE).log("Forcing ChunkLighting Thread %s to stop:\n%s", this.thread, sb.toString());
                    this.thread.stop();
                    break;
                }
            }
        }
        catch (final InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }
    }
    
    public void init(final WorldChunk worldChunk) {
        this.lightCalculation.init(worldChunk);
    }
    
    public void addToQueue(final Vector3i chunkPosition) {
        if (this.set.add(chunkPosition)) {
            synchronized (this.queue) {
                this.queue.enqueue(chunkPosition);
            }
            this.semaphore.release(1);
        }
    }
    
    public boolean isQueued(final int chunkX, final int chunkZ) {
        final Vector3i chunkPos = new Vector3i(chunkX, 0, chunkZ);
        for (int chunkY = 0; chunkY < 10; ++chunkY) {
            chunkPos.setY(chunkY);
            if (this.isQueued(chunkPos)) {
                return true;
            }
        }
        return false;
    }
    
    public boolean isQueued(final Vector3i chunkPosition) {
        return this.set.contains(chunkPosition);
    }
    
    public int getQueueSize() {
        synchronized (this.queue) {
            return this.queue.size();
        }
    }
    
    public boolean invalidateLightAtBlock(final WorldChunk worldChunk, final int blockX, final int blockY, final int blockZ, final BlockType blockType, final int oldHeight, final int newHeight) {
        return this.lightCalculation.invalidateLightAtBlock(worldChunk, blockX, blockY, blockZ, blockType, oldHeight, newHeight);
    }
    
    public boolean invalidateLightInChunk(final WorldChunk worldChunk) {
        return this.lightCalculation.invalidateLightInChunkSections(worldChunk, 0, 10);
    }
    
    public boolean invalidateLightInChunkSection(final WorldChunk worldChunk, final int sectionIndex) {
        return this.lightCalculation.invalidateLightInChunkSections(worldChunk, sectionIndex, sectionIndex + 1);
    }
    
    public boolean invalidateLightInChunkSections(final WorldChunk worldChunk, final int sectionIndexFrom, final int sectionIndexTo) {
        return this.lightCalculation.invalidateLightInChunkSections(worldChunk, sectionIndexFrom, sectionIndexTo);
    }
    
    public void invalidateLoadedChunks() {
        this.world.getChunkStore().getStore().forEachEntityParallel(WorldChunk.getComponentType(), (index, archetypeChunk, storeCommandBuffer) -> {
            final WorldChunk chunk = archetypeChunk.getComponent(index, WorldChunk.getComponentType());
            for (int y = 0; y < 10; ++y) {
                final BlockSection section = chunk.getBlockChunk().getSectionAtIndex(y);
                section.invalidateLocalLight();
                if (BlockChunk.SEND_LOCAL_LIGHTING_DATA || BlockChunk.SEND_GLOBAL_LIGHTING_DATA) {
                    chunk.getBlockChunk().invalidateChunkSection(y);
                }
            }
            return;
        });
        this.world.getChunkStore().getChunkIndexes().forEach(index -> {
            final int x = ChunkUtil.xOfChunkIndex(index);
            final int z = ChunkUtil.zOfChunkIndex(index);
            for (int y2 = 0; y2 < 10; ++y2) {
                this.addToQueue(new Vector3i(x, y2, z));
            }
        });
    }
}
