package eu.crushedpixel.replaymod.renderer;

import eu.crushedpixel.replaymod.utils.JailingQueue;
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
import net.minecraft.client.renderer.chunk.ChunkRenderWorker;
import net.minecraft.client.renderer.chunk.RenderChunk;
import net.minecraft.client.renderer.culling.ICamera;
import net.minecraft.entity.Entity;
import net.minecraft.util.BlockPos;

import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;

public class ChunkLoadingRenderGlobal {

    private final RenderGlobal hooked;
    private final ChunkRenderDispatcher renderDispatcher;
    private final JailingQueue<?> workerJailingQueue;
    private final CustomChunkRenderWorker renderWorker;
    private int frame;

    @SuppressWarnings("unchecked")
    public ChunkLoadingRenderGlobal(RenderGlobal renderGlobal) {
        this.hooked = renderGlobal;
        this.renderDispatcher = renderGlobal.renderDispatcher;
        this.renderWorker = new CustomChunkRenderWorker(renderDispatcher, new RegionRenderCacheBuilder());

        int workerThreads = renderDispatcher.listThreadedWorkers.size();
        BlockingQueue<Object> queueChunkUpdates = renderDispatcher.queueChunkUpdates;
        workerJailingQueue = new JailingQueue<Object>(queueChunkUpdates);
        renderDispatcher.queueChunkUpdates = workerJailingQueue;
        ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null);
        element.finish();
        for (int i = 0; i < workerThreads; i++) {
            queueChunkUpdates.add(element);
        }
        workerJailingQueue.jail(workerThreads);
        renderDispatcher.queueChunkUpdates = queueChunkUpdates;

        try {
            Field hookField = RenderGlobal.class.getField("hook");
            hookField.set(hooked, this);
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        }
    }

    @SuppressWarnings("unused") // Method called by ASM hook
    public void setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, int frameCount, boolean playerSpectator) {
        // There has to be a better way to force minecraft into queueing all chunks at once
        // Someone should probably find and implement it!
        do {
            original_setupTerrain(viewEntity, partialTicks, camera, frame++, playerSpectator);
        } while (hooked.displayListEntitiesDirty);
    }

    @SuppressWarnings("unused") // Method called by ASM hook
    public boolean isPositionInRenderChunk(BlockPos pos, RenderChunk chunk) {
        return true;
    }

    @SuppressWarnings({"unchecked", "unused"}) // Method called by ASM hook
    public void updateChunks(long finishTimeNano) {
        while (renderDispatcher.runChunkUploads(0)) {
            hooked.displayListEntitiesDirty = true;
        }

        while (!renderDispatcher.queueChunkUpdates.isEmpty()) {
            try {
                renderWorker.processTask((ChunkCompileTaskGenerator) renderDispatcher.queueChunkUpdates.poll());
            } catch (InterruptedException ignored) { }
        }

        Iterator<RenderChunk> iterator = hooked.chunksToUpdate.iterator();
        while (iterator.hasNext()) {
            RenderChunk renderchunk = iterator.next();

            renderDispatcher.updateChunkNow(renderchunk);

            renderchunk.setNeedsUpdate(false);
            iterator.remove();
        }
    }

    @SuppressWarnings("unused")
    private void original_setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, int frameCount, boolean playerSpectator) {
        // Method body generated by ASM
    }

    @SuppressWarnings("unused")
    private boolean original_isPositionInRenderChunk(BlockPos pos, RenderChunk chunk) {
        // Method body generated by ASM
        return false;
    }

    @SuppressWarnings("unused")
    private void original_updateChunks(long finishTimeNano) {
        // Method body generated by ASM
    }

    public void uninstall() {
        workerJailingQueue.freeAll();

        try {
            Field hookField = RenderGlobal.class.getField("hook");
            hookField.set(hooked, null);
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        }
    }

    /**
     * Custom ChunkRenderWorker class providing access to the protected processTask method
     */
    private static class CustomChunkRenderWorker extends ChunkRenderWorker {
        public CustomChunkRenderWorker(ChunkRenderDispatcher p_i46202_1_, RegionRenderCacheBuilder p_i46202_2_) {
            super(p_i46202_1_, p_i46202_2_);
        }

        @Override
        protected void processTask(ChunkCompileTaskGenerator p_178474_1_) throws InterruptedException {
            super.processTask(p_178474_1_);
        }
    }
}
