package com.skcraft.plume.module;

import com.google.common.base.Supplier;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.sk89q.worldedit.util.eventbus.Subscribe;
import com.skcraft.plume.common.event.lifecycle.LoadConfigEvent;
import com.skcraft.plume.common.util.config.Config;
import com.skcraft.plume.common.util.config.InjectConfig;
import com.skcraft.plume.common.util.module.Module;
import com.skcraft.plume.event.tick.EntityTickEvent;
import com.skcraft.plume.event.tick.EntityTickExceptionEvent;
import com.skcraft.plume.event.tick.TileEntityTickEvent;
import com.skcraft.plume.event.tick.TileEntityTickExceptionEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import lombok.extern.java.Log;
import net.minecraft.entity.Entity;
import net.minecraft.tileentity.TileEntity;
import ninja.leaping.configurate.objectmapping.Setting;

import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

@Module(name = "crash-interceptor", desc = "Intercepts certain kinds of crashes", enabled = false)
@Log
public class CrashInterceptor {

    @InjectConfig("crash_interceptor")
    private Config<CrashConfig> config;
    private Cache<Object, Boolean> noTickCache;
    private final LoadingCache<Object, CrashTracker> logCache = CacheBuilder.newBuilder()
            .concurrencyLevel(1)
            .weakKeys()
            .build(new CacheLoader<Object, CrashTracker>() {
                @Override
                public CrashTracker load(Object key) throws Exception {
                    return new CrashTracker();
                }
            });

    @Subscribe
    public void onLoadConfig(LoadConfigEvent event) {
        noTickCache = CacheBuilder.newBuilder()
                .concurrencyLevel(1)
                .weakKeys()
                .expireAfterAccess(config.get().disableTime, TimeUnit.MILLISECONDS)
                .build();
    }

    @SubscribeEvent
    public void onEntityTick(EntityTickEvent event) {
        if (noTickCache.asMap().containsKey(event.getEntity())) {
            event.setCanceled(true);
        }
    }

    @SubscribeEvent
    public void onEntityTickException(EntityTickExceptionEvent event) {
        if (config.get().crashResponse == CrashResponse.SUPPRESS_AND_LOG) {
            noTickCache.put(event.getEntity(), true);
            logCache.getUnchecked(event.getEntity()).log(event.getThrowable(), () -> getEntityString(event.getEntity()));
            event.setCanceled(true);
        }
    }

    @SubscribeEvent
    public void onTileEntityTick(TileEntityTickEvent event) {
        if (noTickCache.asMap().containsKey(event.getTileEntity())) {
            event.setCanceled(true);
        }
    }

    @SubscribeEvent
    public void onTileEntityTickException(TileEntityTickExceptionEvent event) {
        if (config.get().crashResponse == CrashResponse.SUPPRESS_AND_LOG) {
            noTickCache.put(event.getTileEntity(), true);
            logCache.getUnchecked(event.getTileEntity()).log(event.getThrowable(), () -> getTileEntityString(event.getTileEntity()));
            event.setCanceled(true);
        }
    }

    private static String getEntityString(Entity entity) {
        return "Entity: {dim=" + entity.worldObj.provider.dimensionId +
                " pos=" + entity.posX + "," + entity.posY + "," + entity.posZ +
                " (" + entity.getClass().getName() + ")}";
    }

    private static String getTileEntityString(TileEntity tileEntity) {
        return "TileEntity: {dim=" + tileEntity.getWorldObj().provider.dimensionId +
                " pos=" + tileEntity.xCoord + "," + tileEntity.yCoord + "," + tileEntity.zCoord +
                " (" + tileEntity.getClass().getName() + ")}";
    }

    private class CrashTracker {
        private long lastMessage;

        public void log(Throwable throwable, Supplier<String> infoString) {
            long now = System.nanoTime();
            if (now - lastMessage > TimeUnit.MILLISECONDS.toNanos(config.get().logInterval)) {
                lastMessage = now;
                log.log(Level.WARNING, "Crash intercepted for " + infoString.get(), throwable);
            }
        }
    }

    private static class CrashConfig {
        @Setting(comment = "The action to take when a crash occurs")
        public CrashResponse crashResponse = CrashResponse.DEFAULT;

        @Setting(comment = "How long to disable a block or entity for if it crashes, in milliseconds")
        public long disableTime = TimeUnit.SECONDS.toMillis(10);

        @Setting(comment = "How long to wait before logging a crash again for a crashing block or entity, in milliseconds")
        public long logInterval = TimeUnit.SECONDS.toMillis(30);
    }

    private enum CrashResponse {
        DEFAULT,
        SUPPRESS_AND_LOG
    }

}

@@ -72,46 +72,8 @@
 er;%0A
-import com.google.common.cache.Cache;%0A
 impo
@@ -109,24 +109,24 @@
 cheBuilder;%0A
+
 import com.g
@@ -206,125 +206,8 @@
 he;%0A
-import com.sk89q.worldedit.util.eventbus.Subscribe;%0Aimport com.skcraft.plume.common.event.lifecycle.LoadConfigEvent;%0A
 impo
@@ -368,61 +368,8 @@
 le;%0A
-import com.skcraft.plume.event.tick.EntityTickEvent;%0A
 impo
@@ -430,65 +430,8 @@
 nt;%0A
-import com.skcraft.plume.event.tick.TileEntityTickEvent;%0A
 impo
@@ -1007,56 +1007,8 @@
 ig;%0A
-    private Cache%3CObject, Boolean%3E noTickCache;%0A
     
@@ -1395,501 +1395,8 @@
 );%0A%0A
-    @Subscribe%0A    public void onLoadConfig(LoadConfigEvent event) %7B%0A        noTickCache = CacheBuilder.newBuilder()%0A                .concurrencyLevel(1)%0A                .weakKeys()%0A                .expireAfterAccess(config.get().disableTime, TimeUnit.MILLISECONDS)%0A                .build();%0A    %7D%0A%0A    @SubscribeEvent%0A    public void onEntityTick(EntityTickEvent event) %7B%0A        if (noTickCache.asMap().containsKey(event.getEntity())) %7B%0A            event.setCanceled(true);%0A        %7D%0A    %7D%0A%0A
     
@@ -1563,62 +1563,8 @@
 ) %7B%0A
-            noTickCache.put(event.getEntity(), true);%0A
     
@@ -1739,214 +1739,8 @@
  %7D%0A%0A
-    @SubscribeEvent%0A    public void onTileEntityTick(TileEntityTickEvent event) %7B%0A        if (noTickCache.asMap().containsKey(event.getTileEntity())) %7B%0A            event.setCanceled(true);%0A        %7D%0A    %7D%0A%0A
     
@@ -1915,66 +1915,8 @@
 ) %7B%0A
-            noTickCache.put(event.getTileEntity(), true);%0A
     
@@ -3247,24 +3247,24 @@
 sh occurs%22)%0A
+
         publ
@@ -3324,177 +3324,8 @@
 T;%0A%0A
-        @Setting(comment = %22How long to disable a block or entity for if it crashes, in milliseconds%22)%0A        public long disableTime = TimeUnit.SECONDS.toMillis(10);%0A%0A
     

Remove the no-tick-cache from the crash interceptor.