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

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

import com.hypixel.hytale.math.util.HashUtil;
import java.util.function.Function;
import com.hypixel.hytale.math.util.ChunkUtil;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.worldgen.util.ObjectPool;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.worldgen.util.cache.SizedTimeoutCache;

public abstract class ExtendedCoordinateCache<K, T>
{
    @Nonnull
    private final SizedTimeoutCache<ExtendedCoordinateKey<K>, T> cache;
    @Nonnull
    private final ExtendedCoordinateObjectFunction<K, T> loader;
    @Nonnull
    private final ObjectPool<ExtendedCoordinateKey<K>> vectorPool;
    
    public ExtendedCoordinateCache(@Nonnull final ExtendedCoordinateObjectFunction<K, T> loader, @Nullable final ExtendedCoordinateRemovalListener<T> removalListener, final int maxSize, final long expireAfterSeconds) {
        this.loader = loader;
        this.vectorPool = new ObjectPool<ExtendedCoordinateKey<K>>(maxSize, ExtendedCoordinateKey::new);
        this.cache = new SizedTimeoutCache<ExtendedCoordinateKey<K>, T>(expireAfterSeconds, TimeUnit.SECONDS, maxSize, key -> {
            final int x = ChunkUtil.xOfChunkIndex(key.coord);
            final int z = ChunkUtil.zOfChunkIndex(key.coord);
            return loader.compute(key.k, key.seed, x, z);
        }, (key, value) -> {
            this.vectorPool.recycle(key);
            if (removalListener != null) {
                removalListener.onRemoval(value);
            }
        });
    }
    
    @Nullable
    public T get(@Nonnull final K k, final int seed, final int x, final int y) {
        return this.cache.getWithReusedKey(this.localKey().setLocation(k, seed, x, y), this.vectorPool);
    }
    
    protected abstract ExtendedCoordinateKey<K> localKey();
    
    public static class ExtendedCoordinateKey<K> implements Function<ExtendedCoordinateKey<K>, ExtendedCoordinateKey<K>>
    {
        @Nullable
        private K k;
        private int seed;
        private long coord;
        private int hash;
        
        public ExtendedCoordinateKey() {
            this(null, 0, 0, 0);
        }
        
        public ExtendedCoordinateKey(@Nullable final K k, final int seed, final int x, final int y) {
            this.k = k;
            this.seed = seed;
            this.coord = ChunkUtil.indexChunk(x, y);
            this.hash = 31 * ((k != null) ? k.hashCode() : 0) + (int)HashUtil.hash(seed, this.coord);
        }
        
        @Nonnull
        public ExtendedCoordinateKey<K> setLocation(@Nonnull final K k, final int seed, final int x, final int y) {
            this.k = k;
            this.seed = seed;
            this.coord = ChunkUtil.indexChunk(x, y);
            this.hash = 31 * k.hashCode() + (int)HashUtil.hash(seed, this.coord);
            return this;
        }
        
        @Nonnull
        @Override
        public ExtendedCoordinateKey<K> apply(@Nonnull final ExtendedCoordinateKey<K> cachedKey) {
            this.k = cachedKey.k;
            this.seed = cachedKey.seed;
            this.coord = cachedKey.coord;
            this.hash = cachedKey.hash;
            return this;
        }
        
        @Override
        public int hashCode() {
            return this.hash;
        }
        
        @Override
        public boolean equals(final Object o) {
            final ExtendedCoordinateKey<?> that = (ExtendedCoordinateKey<?>)o;
            return this.seed == that.seed && this.coord == that.coord && this.k.equals(that.k);
        }
    }
    
    @FunctionalInterface
    public interface ExtendedCoordinateObjectFunction<K, T>
    {
        T compute(final K p0, final int p1, final int p2, final int p3);
    }
    
    @FunctionalInterface
    public interface ExtendedCoordinateRemovalListener<T>
    {
        void onRemoval(final T p0);
    }
}
