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

package com.hypixel.hytale.assetstore.map;

import java.util.List;
import java.util.HashSet;
import it.unimi.dsi.fastutil.ints.IntIterator;
import com.hypixel.hytale.assetstore.AssetExtraInfo;
import java.util.Iterator;
import java.util.Collection;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import com.hypixel.hytale.assetstore.codec.AssetCodec;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.Optional;
import java.util.Arrays;
import java.util.Collections;
import it.unimi.dsi.fastutil.objects.ObjectSets;
import java.util.Objects;
import javax.annotation.Nullable;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
import it.unimi.dsi.fastutil.ints.IntSets;
import java.util.concurrent.ConcurrentHashMap;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.Set;
import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap;
import java.nio.file.Path;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;
import com.hypixel.hytale.assetstore.AssetMap;
import com.hypixel.hytale.assetstore.JsonAsset;

public class DefaultAssetMap<K, T extends JsonAsset<K>> extends AssetMap<K, T>
{
    public static final AssetRef[] EMPTY_PAIR_ARRAY;
    public static final String DEFAULT_PACK_KEY = "Hytale:Hytale";
    protected final StampedLock assetMapLock;
    @Nonnull
    protected final Map<K, T> assetMap;
    protected final Map<K, AssetRef<T>[]> assetChainMap;
    protected final Map<String, ObjectSet<K>> packAssetKeys;
    protected final Map<Path, ObjectSet<K>> pathToKeyMap;
    protected final Map<K, ObjectSet<K>> assetChildren;
    protected final Int2ObjectConcurrentHashMap<Set<K>> tagStorage;
    protected final Int2ObjectConcurrentHashMap<Set<K>> unmodifiableTagStorage;
    protected final IntSet unmodifiableTagKeys;
    
    public DefaultAssetMap() {
        this.assetMapLock = new StampedLock();
        this.packAssetKeys = new ConcurrentHashMap<String, ObjectSet<K>>();
        this.pathToKeyMap = new ConcurrentHashMap<Path, ObjectSet<K>>();
        this.tagStorage = new Int2ObjectConcurrentHashMap<Set<K>>();
        this.unmodifiableTagStorage = new Int2ObjectConcurrentHashMap<Set<K>>();
        this.unmodifiableTagKeys = IntSets.unmodifiable(this.tagStorage.keySet());
        this.assetMap = new Object2ObjectOpenCustomHashMap<K, T>(CaseInsensitiveHashStrategy.getInstance());
        this.assetChainMap = new Object2ObjectOpenCustomHashMap<K, AssetRef<T>[]>(CaseInsensitiveHashStrategy.getInstance());
        this.assetChildren = new Object2ObjectOpenCustomHashMap<K, ObjectSet<K>>(CaseInsensitiveHashStrategy.getInstance());
    }
    
    public DefaultAssetMap(@Nonnull final Map<K, T> assetMap) {
        this.assetMapLock = new StampedLock();
        this.packAssetKeys = new ConcurrentHashMap<String, ObjectSet<K>>();
        this.pathToKeyMap = new ConcurrentHashMap<Path, ObjectSet<K>>();
        this.tagStorage = new Int2ObjectConcurrentHashMap<Set<K>>();
        this.unmodifiableTagStorage = new Int2ObjectConcurrentHashMap<Set<K>>();
        this.unmodifiableTagKeys = IntSets.unmodifiable(this.tagStorage.keySet());
        this.assetMap = assetMap;
        this.assetChainMap = new Object2ObjectOpenCustomHashMap<K, AssetRef<T>[]>(CaseInsensitiveHashStrategy.getInstance());
        this.assetChildren = new Object2ObjectOpenCustomHashMap<K, ObjectSet<K>>(CaseInsensitiveHashStrategy.getInstance());
    }
    
    @Nullable
    @Override
    public T getAsset(final K key) {
        long stamp = this.assetMapLock.tryOptimisticRead();
        final T value = this.assetMap.get(key);
        if (this.assetMapLock.validate(stamp)) {
            return value;
        }
        stamp = this.assetMapLock.readLock();
        try {
            return this.assetMap.get(key);
        }
        finally {
            this.assetMapLock.unlockRead(stamp);
        }
    }
    
    @Nullable
    @Override
    public T getAsset(@Nonnull final String packKey, final K key) {
        long stamp = this.assetMapLock.tryOptimisticRead();
        final T result = this.getAssetForPack0(packKey, key);
        if (this.assetMapLock.validate(stamp)) {
            return result;
        }
        stamp = this.assetMapLock.readLock();
        try {
            return this.getAssetForPack0(packKey, key);
        }
        finally {
            this.assetMapLock.unlockRead(stamp);
        }
    }
    
    private T getAssetForPack0(@Nonnull final String packKey, final K key) {
        final AssetRef<T>[] chain = this.assetChainMap.get(key);
        if (chain == null) {
            return null;
        }
        int i = 0;
        while (i < chain.length) {
            final AssetRef<T> pair = chain[i];
            if (Objects.equals(pair.pack, packKey)) {
                if (i == 0) {
                    return null;
                }
                return chain[i - 1].value;
            }
            else {
                ++i;
            }
        }
        return this.assetMap.get(key);
    }
    
    @Nullable
    @Override
    public Path getPath(final K key) {
        long stamp = this.assetMapLock.tryOptimisticRead();
        final Path result = this.getPath0(key);
        if (this.assetMapLock.validate(stamp)) {
            return result;
        }
        stamp = this.assetMapLock.readLock();
        try {
            return this.getPath0(key);
        }
        finally {
            this.assetMapLock.unlockRead(stamp);
        }
    }
    
    @Nullable
    @Override
    public String getAssetPack(final K key) {
        long stamp = this.assetMapLock.tryOptimisticRead();
        final String result = this.getAssetPack0(key);
        if (this.assetMapLock.validate(stamp)) {
            return result;
        }
        stamp = this.assetMapLock.readLock();
        try {
            return this.getAssetPack0(key);
        }
        finally {
            this.assetMapLock.unlockRead(stamp);
        }
    }
    
    @Nullable
    private Path getPath0(final K key) {
        final AssetRef<T> result = this.getAssetRef(key);
        return (result != null) ? result.path : null;
    }
    
    @Nullable
    private String getAssetPack0(final K key) {
        final AssetRef<T> result = this.getAssetRef(key);
        return (result != null) ? result.pack : null;
    }
    
    @Nullable
    private AssetRef<T> getAssetRef(final K key) {
        final AssetRef<T>[] chain = this.assetChainMap.get(key);
        if (chain == null) {
            return null;
        }
        return chain[chain.length - 1];
    }
    
    @Override
    public Set<K> getKeys(@Nonnull final Path path) {
        final ObjectSet<K> set = this.pathToKeyMap.get(path);
        return (Set<K>)((set == null) ? ObjectSets.emptySet() : ObjectSets.unmodifiable((ObjectSet<?>)set));
    }
    
    @Override
    public Set<K> getChildren(final K key) {
        long stamp = this.assetMapLock.tryOptimisticRead();
        ObjectSet<K> children = this.assetChildren.get(key);
        final Set<K> result = (Set<K>)((children == null) ? ObjectSets.emptySet() : ObjectSets.unmodifiable((ObjectSet<?>)children));
        if (this.assetMapLock.validate(stamp)) {
            return result;
        }
        stamp = this.assetMapLock.readLock();
        try {
            children = this.assetChildren.get(key);
            return (Set<K>)((children == null) ? ObjectSets.emptySet() : ObjectSets.unmodifiable((ObjectSet<?>)children));
        }
        finally {
            this.assetMapLock.unlockRead(stamp);
        }
    }
    
    @Override
    public int getAssetCount() {
        return this.assetMap.size();
    }
    
    @Nonnull
    @Override
    public Map<K, T> getAssetMap() {
        return Collections.unmodifiableMap((Map<? extends K, ? extends T>)this.assetMap);
    }
    
    @Nonnull
    @Override
    public Map<K, Path> getPathMap(@Nonnull final String packKey) {
        final long stamp = this.assetMapLock.readLock();
        try {
            return (Map<K, Path>)this.assetChainMap.entrySet().stream().map(e -> Map.entry(e.getKey(), Arrays.stream(e.getValue()).filter(v -> Objects.equals(v.pack, packKey)).findFirst())).filter(e -> e.getValue().isPresent()).filter(e -> e.getValue().get().path != null).collect(Collectors.toMap((Function<? super Object, ?>)Map.Entry::getKey, e -> e.getValue().get().path));
        }
        finally {
            this.assetMapLock.unlockRead(stamp);
        }
    }
    
    @Override
    public Set<K> getKeysForTag(final int tagIndex) {
        return this.unmodifiableTagStorage.getOrDefault(tagIndex, (Set<K>)ObjectSets.emptySet());
    }
    
    @Nonnull
    @Override
    public IntSet getTagIndexes() {
        return this.unmodifiableTagKeys;
    }
    
    @Override
    public int getTagCount() {
        return this.tagStorage.size();
    }
    
    @Override
    protected void clear() {
        final long stamp = this.assetMapLock.writeLock();
        try {
            this.assetChildren.clear();
            this.assetChainMap.clear();
            this.pathToKeyMap.clear();
            this.assetMap.clear();
            this.tagStorage.clear();
            this.unmodifiableTagStorage.clear();
        }
        finally {
            this.assetMapLock.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) {
        final long stamp = this.assetMapLock.writeLock();
        try {
            for (final Map.Entry<K, Set<K>> entry : loadedAssetChildren.entrySet()) {
                this.assetChildren.computeIfAbsent(entry.getKey(), k -> new ObjectOpenHashSet(3)).addAll(entry.getValue());
            }
            for (final Map.Entry<K, Path> entry2 : loadedKeyToPathMap.entrySet()) {
                this.pathToKeyMap.computeIfAbsent((Path)entry2.getValue(), k -> new ObjectOpenHashSet(1)).add(entry2.getKey());
            }
            for (final Map.Entry<K, T> e : loadedAssets.entrySet()) {
                final K key = e.getKey();
                this.packAssetKeys.computeIfAbsent(packKey, v -> new ObjectOpenHashSet()).add(key);
                AssetRef<T>[] chain = this.assetChainMap.get(key);
                if (chain == null) {
                    chain = DefaultAssetMap.EMPTY_PAIR_ARRAY;
                }
                boolean found = false;
                for (final AssetRef<T> pair : chain) {
                    if (Objects.equals(pair.pack, packKey)) {
                        pair.value = e.getValue();
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    chain = Arrays.copyOf(chain, chain.length + 1);
                    chain[chain.length - 1] = new AssetRef<T>(packKey, loadedKeyToPathMap.get(e.getKey()), e.getValue());
                    this.assetChainMap.put(key, chain);
                }
                final T finalVal = chain[chain.length - 1].value;
                this.assetMap.put(key, finalVal);
            }
        }
        finally {
            this.assetMapLock.unlockWrite(stamp);
        }
        this.putAssetTags(codec, loadedAssets);
    }
    
    protected void putAssetTags(@Nonnull final AssetCodec<K, T> codec, @Nonnull final Map<K, T> loadedAssets) {
        for (final Map.Entry<K, T> entry : loadedAssets.entrySet()) {
            final AssetExtraInfo.Data data = codec.getData(entry.getValue());
            if (data == null) {
                continue;
            }
            final K key = entry.getKey();
            final IntIterator iterator = data.getExpandedTagIndexes().iterator();
            while (iterator.hasNext()) {
                final int tag = iterator.nextInt();
                this.putAssetTag(key, tag);
            }
        }
    }
    
    protected void putAssetTag(final K key, final int tag) {
        this.tagStorage.computeIfAbsent(tag, k -> {
            final ObjectOpenHashSet<K> set = new ObjectOpenHashSet<K>(3);
            this.unmodifiableTagStorage.put(k, (Set<K>)ObjectSets.unmodifiable((ObjectSet<?>)set));
            return set;
        }).add(key);
    }
    
    @Override
    public Set<K> getKeysForPack(@Nonnull final String name) {
        return this.packAssetKeys.get(name);
    }
    
    @Override
    protected Set<K> remove(@Nonnull final Set<K> keys) {
        final long stamp = this.assetMapLock.writeLock();
        try {
            final Set<K> children = new HashSet<K>();
            for (final K key : keys) {
                final AssetRef<T>[] chain = this.assetChainMap.remove(key);
                if (chain == null) {
                    continue;
                }
                final AssetRef<T> info = chain[chain.length - 1];
                if (info.path != null) {
                    this.pathToKeyMap.computeIfPresent(info.path, (p, list) -> {
                        list.remove(key);
                        if (list.isEmpty()) {
                            return null;
                        }
                        else {
                            return (ObjectSet<K>)list;
                        }
                    });
                }
                this.assetMap.remove(key);
                for (final AssetRef<T> c : chain) {
                    this.packAssetKeys.get(Objects.requireNonNullElse(c.pack, "Hytale:Hytale")).remove(key);
                }
                for (final ObjectSet<K> child : this.assetChildren.values()) {
                    child.remove(key);
                }
                final ObjectSet<K> child2 = this.assetChildren.remove(key);
                if (child2 == null) {
                    continue;
                }
                children.addAll((Collection<? extends K>)child2);
            }
            this.tagStorage.forEach((_k, value, removedKeys) -> value.removeAll(removedKeys), keys);
            children.removeAll(keys);
            return children;
        }
        finally {
            this.assetMapLock.unlockWrite(stamp);
        }
    }
    
    @Override
    protected Set<K> remove(@Nonnull final String packKey, @Nonnull final Set<K> keys, @Nonnull final List<Map.Entry<String, Object>> pathsToReload) {
        final long stamp = this.assetMapLock.writeLock();
        try {
            final Set<K> children = new HashSet<K>();
            final ObjectSet<K> packKeys = this.packAssetKeys.get(Objects.requireNonNullElse(packKey, "Hytale:Hytale"));
            if (packKeys == null) {
                return Collections.emptySet();
            }
            final Iterator<K> iterator = keys.iterator();
            while (iterator.hasNext()) {
                final K key = iterator.next();
                packKeys.remove(key);
                final AssetRef<T>[] chain = this.assetChainMap.remove(key);
                if (chain.length == 1) {
                    final AssetRef<T> info = chain[0];
                    if (!Objects.equals(info.pack, packKey)) {
                        iterator.remove();
                        this.assetChainMap.put(key, chain);
                    }
                    else {
                        if (info.path != null) {
                            this.pathToKeyMap.computeIfPresent(info.path, (p, list) -> {
                                list.remove(key);
                                if (list.isEmpty()) {
                                    return null;
                                }
                                else {
                                    return (ObjectSet<K>)list;
                                }
                            });
                        }
                        this.assetMap.remove(key);
                        for (final ObjectSet<K> child : this.assetChildren.values()) {
                            child.remove(key);
                        }
                        final ObjectSet<K> child2 = this.assetChildren.remove(key);
                        if (child2 == null) {
                            continue;
                        }
                        children.addAll((Collection<? extends K>)child2);
                    }
                }
                else {
                    iterator.remove();
                    final AssetRef<T>[] newChain = new AssetRef[chain.length - 1];
                    int offset = 0;
                    for (int i = 0; i < chain.length; ++i) {
                        final AssetRef<T> pair = chain[i];
                        if (Objects.equals(pair.pack, packKey)) {
                            if (pair.path != null) {
                                this.pathToKeyMap.computeIfPresent(pair.path, (p, list) -> {
                                    list.remove(key);
                                    if (list.isEmpty()) {
                                        return null;
                                    }
                                    else {
                                        return (ObjectSet<K>)list;
                                    }
                                });
                            }
                        }
                        else {
                            newChain[offset++] = pair;
                            if (pair.path != null) {
                                pathsToReload.add((Map.Entry<String, Object>)Map.entry(pair.pack, pair.path));
                            }
                            else {
                                pathsToReload.add((Map.Entry<String, Object>)Map.entry(pair.pack, pair.value));
                            }
                        }
                    }
                    this.assetChainMap.put(key, newChain);
                    final AssetRef<T> newAsset = newChain[newChain.length - 1];
                    this.assetMap.put(key, newAsset.value);
                    if (newAsset.path == null) {
                        continue;
                    }
                    this.pathToKeyMap.computeIfAbsent(newAsset.path, k -> new ObjectOpenHashSet(1)).add(key);
                }
            }
            this.tagStorage.forEach((_k, value, removedKeys) -> value.removeAll(removedKeys), keys);
            children.removeAll(keys);
            return children;
        }
        finally {
            this.assetMapLock.unlockWrite(stamp);
        }
    }
    
    static {
        EMPTY_PAIR_ARRAY = new AssetRef[0];
    }
    
    protected static class AssetRef<T>
    {
        protected final String pack;
        protected final Path path;
        protected T value;
        
        protected AssetRef(final String pack, final Path path, final T value) {
            this.pack = pack;
            this.path = path;
            this.value = value;
        }
    }
}
