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

package com.hypixel.hytale.assetstore.map;

import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.List;
import java.util.Iterator;
import java.util.Set;
import java.nio.file.Path;
import java.util.Map;
import com.hypixel.hytale.assetstore.codec.AssetCodec;
import javax.annotation.Nullable;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nonnull;
import java.util.function.IntFunction;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.concurrent.locks.StampedLock;
import java.util.concurrent.atomic.AtomicInteger;

public class IndexedLookupTableAssetMap<K, T extends JsonAssetWithMap<K, IndexedLookupTableAssetMap<K, T>>> extends AssetMapWithIndexes<K, T>
{
    private final AtomicInteger nextIndex;
    private final StampedLock keyToIndexLock;
    private final Object2IntMap<K> keyToIndex;
    @Nonnull
    private final IntFunction<T[]> arrayProvider;
    private final ReentrantLock arrayLock;
    private T[] array;
    
    public IndexedLookupTableAssetMap(@Nonnull final IntFunction<T[]> arrayProvider) {
        this.nextIndex = new AtomicInteger();
        this.keyToIndexLock = new StampedLock();
        this.keyToIndex = new Object2IntOpenCustomHashMap<K>(CaseInsensitiveHashStrategy.getInstance());
        this.arrayLock = new ReentrantLock();
        this.arrayProvider = arrayProvider;
        this.array = arrayProvider.apply(0);
        this.keyToIndex.defaultReturnValue(Integer.MIN_VALUE);
    }
    
    public int getIndex(final K key) {
        long stamp = this.keyToIndexLock.tryOptimisticRead();
        final int value = this.keyToIndex.getInt(key);
        if (this.keyToIndexLock.validate(stamp)) {
            return value;
        }
        stamp = this.keyToIndexLock.readLock();
        try {
            return this.keyToIndex.getInt(key);
        }
        finally {
            this.keyToIndexLock.unlockRead(stamp);
        }
    }
    
    public int getIndexOrDefault(final K key, final int def) {
        long stamp = this.keyToIndexLock.tryOptimisticRead();
        final int value = this.keyToIndex.getOrDefault(key, def);
        if (this.keyToIndexLock.validate(stamp)) {
            return value;
        }
        stamp = this.keyToIndexLock.readLock();
        try {
            return this.keyToIndex.getOrDefault(key, def);
        }
        finally {
            this.keyToIndexLock.unlockRead(stamp);
        }
    }
    
    public int getNextIndex() {
        return this.nextIndex.get();
    }
    
    @Nullable
    public T getAsset(final int index) {
        if (index < 0 || index >= this.array.length) {
            return null;
        }
        return this.array[index];
    }
    
    public T getAssetOrDefault(final int index, final T def) {
        if (index < 0 || index >= this.array.length) {
            return def;
        }
        return this.array[index];
    }
    
    @Override
    protected void clear() {
        super.clear();
        final long stamp = this.keyToIndexLock.writeLock();
        this.arrayLock.lock();
        try {
            this.keyToIndex.clear();
            this.array = this.arrayProvider.apply(0);
        }
        finally {
            this.arrayLock.unlock();
            this.keyToIndexLock.unlockWrite(stamp);
        }
    }
    
    @Override
    protected void putAll(@Nonnull final String packKey, @Nonnull final AssetCodec<K, T> codec, @Nonnull final Map<K, T> loadedAssets, @Nonnull final Map<K, Path> loadedKeyToPathMap, @Nonnull final Map<K, Set<K>> loadedAssetChildren) {
        super.putAll(packKey, codec, loadedAssets, loadedKeyToPathMap, loadedAssetChildren);
        this.putAll0(codec, loadedAssets);
    }
    
    private void putAll0(@Nonnull final AssetCodec<K, T> codec, @Nonnull final Map<K, T> loadedAssets) {
        final long stamp = this.keyToIndexLock.writeLock();
        this.arrayLock.lock();
        try {
            int highestIndex = 0;
            for (final K key : loadedAssets.keySet()) {
                int index = this.keyToIndex.getInt(key);
                if (index == Integer.MIN_VALUE) {
                    this.keyToIndex.put(key, index = this.nextIndex.getAndIncrement());
                }
                if (index < 0) {
                    throw new IllegalArgumentException("Index can't be less than zero!");
                }
                if (index <= highestIndex) {
                    continue;
                }
                highestIndex = index;
            }
            final int length = highestIndex + 1;
            if (length < 0) {
                throw new IllegalArgumentException("Highest index can't be less than zero!");
            }
            if (length > this.array.length) {
                final T[] newArray = this.arrayProvider.apply(length);
                System.arraycopy(this.array, 0, newArray, 0, this.array.length);
                this.array = newArray;
            }
            for (final Map.Entry<K, T> entry : loadedAssets.entrySet()) {
                final K key2 = entry.getKey();
                final int index2 = this.keyToIndex.getInt(key2);
                if (index2 < 0) {
                    throw new IllegalArgumentException("Index can't be less than zero!");
                }
                final T value = entry.getValue();
                this.putAssetTag(codec, key2, index2, this.array[index2] = value);
            }
        }
        finally {
            this.arrayLock.unlock();
            this.keyToIndexLock.unlockWrite(stamp);
        }
    }
    
    @Override
    protected Set<K> remove(@Nonnull final Set<K> keys) {
        final Set<K> remove = super.remove(keys);
        this.remove0(keys);
        return remove;
    }
    
    @Override
    protected Set<K> remove(@Nonnull final String packKey, @Nonnull final Set<K> keys, @Nonnull final List<Map.Entry<String, Object>> pathsToReload) {
        final Set<K> remove = super.remove(packKey, keys, pathsToReload);
        this.remove0(keys);
        return remove;
    }
    
    private void remove0(@Nonnull final Set<K> keys) {
        final long stamp = this.keyToIndexLock.writeLock();
        this.arrayLock.lock();
        try {
            for (final K key : keys) {
                final int blockId = this.keyToIndex.getInt(key);
                if (blockId != Integer.MIN_VALUE) {
                    this.array[blockId] = null;
                    this.indexedTagStorage.forEachWithInt((_k, value, id) -> value.remove(id), blockId);
                }
            }
            int i;
            for (i = this.array.length - 1; i > 0 && this.array[i] == null; --i) {}
            final int length = i + 1;
            if (length != this.array.length) {
                final T[] newArray = this.arrayProvider.apply(length);
                System.arraycopy(this.array, 0, newArray, 0, newArray.length);
                this.array = newArray;
            }
        }
        finally {
            this.arrayLock.unlock();
            this.keyToIndexLock.unlockWrite(stamp);
        }
    }
}
