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

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

import com.hypixel.hytale.server.spawning.SpawningContext;
import com.hypixel.hytale.server.spawning.ISpawnableWithModel;
import com.hypixel.hytale.component.ComponentAccessor;
import java.util.List;
import com.hypixel.hytale.server.core.universe.world.World;
import java.util.logging.Level;
import com.hypixel.hytale.server.spawning.SpawningPlugin;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.Store;
import javax.annotation.Nonnull;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.server.spawning.jobs.SpawnJob;

public abstract class SpawnJobSystem<J extends SpawnJob, T extends SpawnController<J>> extends EntityTickingSystem<EntityStore>
{
    private static final HytaleLogger LOGGER;
    private static final int JOB_BUDGET = 64;
    
    protected void tickSpawnJobs(@Nonnull final T spawnController, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        final World world = store.getExternalData().getWorld();
        if (world.getPlayerCount() == 0 || !world.getWorldConfig().isSpawningNPC() || spawnController.isUnspawnable() || world.getChunkStore().getStore().getEntityCount() == 0) {
            return;
        }
        if (spawnController.getActualNPCs() > spawnController.getExpectedNPCs()) {
            return;
        }
        int blockBudget = SpawningPlugin.get().getTickColumnBudget() / world.getTps();
        try {
            while (blockBudget > 0 && spawnController.getActiveJobCount() != 0) {
                int jobIndex = 0;
                while (jobIndex >= 0 && jobIndex < spawnController.getActiveJobCount() && blockBudget > 0) {
                    final J job = spawnController.getSpawnJob(jobIndex);
                    if (job != null) {
                        job.setColumnBudget(Math.min(64, blockBudget));
                        final Result result = this.runJob(spawnController, job, commandBuffer);
                        blockBudget -= job.getBudgetUsed();
                        if (result != Result.TRY_AGAIN) {
                            final List<J> activeJobs = spawnController.getActiveJobs();
                            jobIndex = activeJobs.indexOf(job);
                            if (jobIndex == -1) {
                                continue;
                            }
                            activeJobs.remove(jobIndex);
                            if (result == Result.PENDING_SPAWN) {
                                continue;
                            }
                            spawnController.addIdleJob(job);
                        }
                        else {
                            ++jobIndex;
                        }
                    }
                    else {
                        jobIndex = -1;
                    }
                }
            }
        }
        catch (final Throwable t) {
            SpawnJobSystem.LOGGER.at(Level.SEVERE).withCause(t).log("Failed to tick Spawn Jobs: ");
        }
    }
    
    protected void onStartRun(@Nonnull final J spawnJob) {
        spawnJob.setBudgetUsed(0);
    }
    
    protected abstract void onEndProbing(final T p0, final J p1, final Result p2, final ComponentAccessor<EntityStore> p3);
    
    protected abstract boolean pickSpawnPosition(final T p0, final J p1, final CommandBuffer<EntityStore> p2);
    
    protected abstract Result trySpawn(final T p0, final J p1, final CommandBuffer<EntityStore> p2);
    
    protected abstract Result spawn(final World p0, final T p1, final J p2, final CommandBuffer<EntityStore> p3);
    
    protected Result endProbing(final T spawnController, @Nonnull final J spawnJob, final Result result, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (!spawnJob.isTerminated()) {
            this.onEndProbing(spawnController, spawnJob, result, componentAccessor);
            spawnJob.reset();
            spawnJob.setTerminated(true);
        }
        return result;
    }
    
    private Result runJob(final T spawnController, @Nonnull final J spawnJob, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        this.onStartRun(spawnJob);
        if (spawnJob.shouldTerminate()) {
            return Result.FAILED;
        }
        ISpawnableWithModel spawnable;
        try {
            spawnable = spawnJob.getSpawnable();
        }
        catch (final IllegalArgumentException e) {
            this.endProbing(spawnController, spawnJob, Result.PERMANENT_FAILURE, commandBuffer);
            throw e;
        }
        if (spawnable == null) {
            final HytaleLogger.Api context = SpawnJobSystem.LOGGER.at(Level.FINEST);
            if (context.isEnabled()) {
                context.log("Spawn job %s: Terminated, spawnable %s gone", spawnJob.getJobId(), spawnJob.getSpawnableName());
            }
            return this.endProbing(spawnController, spawnJob, Result.FAILED, commandBuffer);
        }
        final SpawningContext spawningContext = spawnJob.getSpawningContext();
        if (!spawningContext.setSpawnable(spawnable)) {
            final HytaleLogger.Api context2 = SpawnJobSystem.LOGGER.at(Level.FINEST);
            if (context2.isEnabled()) {
                context2.log("Spawn job %s: Terminated, Unable to set spawnable %s", spawnJob.getJobId(), spawnJob.getSpawnableName());
            }
            return this.endProbing(spawnController, spawnJob, Result.FAILED, commandBuffer);
        }
        try {
            while (spawnJob.budgetAvailable()) {
                if (spawnJob.shouldTerminate()) {
                    SpawnJobSystem.LOGGER.at(Level.FINEST).log("Spawn job %s: Terminated", spawnJob.getJobId());
                    return Result.FAILED;
                }
                if (!this.pickSpawnPosition(spawnController, spawnJob, commandBuffer)) {
                    return this.endProbing(spawnController, spawnJob, Result.FAILED, commandBuffer);
                }
                final Result result = this.trySpawn(spawnController, spawnJob, commandBuffer);
                if (result != Result.TRY_AGAIN) {
                    return result;
                }
            }
        }
        finally {
            spawningContext.release();
        }
        return Result.TRY_AGAIN;
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    public enum Result
    {
        SUCCESS, 
        FAILED, 
        TRY_AGAIN, 
        PERMANENT_FAILURE, 
        PENDING_SPAWN;
    }
}
