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

package com.hypixel.hytale.server.worldgen.util.cache;

import java.util.Iterator;
import java.lang.ref.WeakReference;
import com.hypixel.hytale.server.core.HytaleServer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.lang.ref.Cleaner;
import java.util.concurrent.ScheduledFuture;
import javax.annotation.Nullable;
import java.util.function.BiConsumer;
import javax.annotation.Nonnull;
import java.util.function.Function;
import java.util.Map;

public class TimeoutCache<K, V> implements Cache<K, V>
{
    private final Map<K, CacheEntry<V>> map;
    private final long timeout;
    @Nonnull
    private final Function<K, V> func;
    @Nullable
    private final BiConsumer<K, V> destroyer;
    @Nonnull
    private final ScheduledFuture<?> future;
    @Nonnull
    private final Cleaner.Cleanable cleanable;
    
    public TimeoutCache(final long expire, @Nonnull final TimeUnit unit, @Nonnull final Function<K, V> func, @Nullable final BiConsumer<K, V> destroyer) {
        this.map = new ConcurrentHashMap<K, CacheEntry<V>>();
        this.timeout = unit.toNanos(expire);
        this.func = func;
        this.destroyer = destroyer;
        this.future = HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(new CleanupRunnable<Object, Object>(new WeakReference<Cache<?, ?>>(this)), expire, expire, unit);
        this.cleanable = CleanupFutureAction.CLEANER.register(this, new CleanupFutureAction(this.future));
    }
    
    @Override
    public void cleanup() {
        final long expire = System.nanoTime() - this.timeout;
        for (final Map.Entry<K, CacheEntry<V>> entry : this.map.entrySet()) {
            final CacheEntry<V> cacheEntry = entry.getValue();
            if (cacheEntry.timestamp < expire) {
                final K key = entry.getKey();
                if (!this.map.remove(key, entry.getValue()) || this.destroyer == null) {
                    continue;
                }
                this.destroyer.accept(key, cacheEntry.value);
            }
        }
    }
    
    @Override
    public void shutdown() {
        this.cleanable.clean();
        final Iterator<Map.Entry<K, CacheEntry<V>>> iterator = this.map.entrySet().iterator();
        while (iterator.hasNext()) {
            final Map.Entry<K, CacheEntry<V>> entry = iterator.next();
            final K key = entry.getKey();
            final CacheEntry<V> cacheEntry = entry.getValue();
            if (this.map.remove(key, cacheEntry)) {
                iterator.remove();
                if (this.destroyer == null) {
                    continue;
                }
                this.destroyer.accept(key, cacheEntry.value);
            }
        }
    }
    
    @Override
    public V get(final K key) {
        if (this.future.isCancelled()) {
            throw new IllegalStateException("Cache has been shutdown!");
        }
        final CacheEntry<V> cacheEntry = this.map.compute(key, (k, v) -> {
            if (v != null) {
                v.timestamp = System.nanoTime();
                return v;
            }
            else {
                return new CacheEntry((V)this.func.apply((K)k));
            }
        });
        return cacheEntry.value;
    }
    
    private static class CacheEntry<V>
    {
        private final V value;
        private long timestamp;
        
        public CacheEntry(final V value) {
            this.value = value;
            this.timestamp = System.nanoTime();
        }
    }
}
