package pers.neige.neigeitems.libs.bot.inker.bukkit.nbt;

import com.google.common.collect.ImmutableSet;
import lombok.NonNull;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pers.neige.neigeitems.libs.bot.inker.bukkit.nbt.api.NbtComponentLike;
import pers.neige.neigeitems.libs.bot.inker.bukkit.nbt.internal.annotation.CbVersion;
import pers.neige.neigeitems.libs.bot.inker.bukkit.nbt.internal.invoke.InvokeUtil;
import pers.neige.neigeitems.libs.bot.inker.bukkit.nbt.internal.loader.DelegateAbstractMap;
import pers.neige.neigeitems.ref.nbt.RefCraftMetaItem;
import pers.neige.neigeitems.ref.nbt.RefNbtBase;

import java.util.*;

public class NbtBukkitItemComponent implements NbtComponentLike {
    private static final Logger logger = LoggerFactory.getLogger(NbtBukkitItemComponent.class.getSimpleName());

    private static final Set<String> HANDLED_TAGS = CbVersion.v1_20_R4.isSupport() ? new HashSet<>() : ImmutableSet.copyOf(RefCraftMetaItem.HANDLED_TAGS);

    private final Map<String, Nbt<?>> delegateMap;
    private final ItemStack itemStack;
    private final RefCraftMetaItem refItemMeta;
    private Set<Entry<String, Nbt<?>>> entrySet;

    NbtBukkitItemComponent(ItemStack itemStack) {
        this.itemStack = itemStack;
        this.delegateMap = new DelegateAbstractMap<>(this);
        ItemMeta meta = InvokeUtil.getItemMeta(itemStack);
        if (meta == null) {
            meta = itemStack.getItemMeta();
            InvokeUtil.setItemMeta(itemStack, meta);
        }
        refItemMeta = (RefCraftMetaItem) (Object) meta;
    }

    @Override
    public Nbt<?> get(String key) {
        if (HANDLED_TAGS.contains(key)) {
            logger.warn("key {} isn't support for direct access", key);
            return null;
        } else {
            return Nbt.fromNms(refItemMeta.unhandledTags.get(key));
        }
    }

    @Override
    public boolean containsKey(String key) {
        if (HANDLED_TAGS.contains(key)) {
            logger.warn("key {} isn't support for direct access", key);
            return false;
        } else {
            return refItemMeta.unhandledTags.containsKey(key);
        }
    }

    @Override
    public Nbt<?> put(String key, Nbt<?> value) {
        if (HANDLED_TAGS.contains(key)) {
            logger.warn("key {} isn't support for direct access", key);
            return null;
        } else {
            return Nbt.fromNms(refItemMeta.unhandledTags.put(key, value.delegate));
        }
    }

    @Override
    public Nbt<?> remove(String key) {
        if (HANDLED_TAGS.contains(key)) {
            logger.warn("key {} isn't support for direct access", key);
            return null;
        } else {
            return Nbt.fromNms(refItemMeta.unhandledTags.remove(key));
        }
    }

    @Override
    public @NonNull NbtBukkitItemComponent clone() {
        return new NbtBukkitItemComponent(NbtUtils.asBukkitCopy(itemStack));
    }

    @Override
    public int size() {
        return refItemMeta.unhandledTags.size();
    }

    @Override
    public byte getId() {
        return NbtType.TAG_COMPOUND;
    }

    @Override
    public String getAsString() {
        return itemStack.toString();
    }

    @Override
    public void putAll(@NonNull Map<? extends String, ? extends Nbt<?>> m) {
        delegateMap.putAll(m);
    }

    @Override
    public void clear() {
        delegateMap.clear();
    }

    @Override
    public boolean isEmpty() {
        return delegateMap.isEmpty();
    }

    @Override
    public boolean containsValue(Object value) {
        return delegateMap.containsValue(value);
    }

    @Override
    public @NonNull Collection<Nbt<?>> values() {
        return delegateMap.values();
    }

    @Override
    public @NonNull Set<String> keySet() {
        return refItemMeta.unhandledTags.keySet();
    }

    @Override
    public @NonNull Set<Entry<String, Nbt<?>>> entrySet() {
        if (entrySet == null) {
            entrySet = new AbstractSet<Entry<String, Nbt<?>>>() {
                @Override
                public @NonNull Iterator<Entry<String, Nbt<?>>> iterator() {
                    return new DelegateIterator(refItemMeta.unhandledTags.entrySet().iterator());
                }

                @Override
                public int size() {
                    return refItemMeta.unhandledTags.size();
                }
            };
        }
        return entrySet;
    }

    private static class DelegateIterator implements Iterator<Entry<String, Nbt<?>>> {
        private final Iterator<Entry<String, RefNbtBase>> delegate;

        private DelegateIterator(Iterator<Entry<String, RefNbtBase>> delegate) {
            this.delegate = delegate;
        }

        @Override
        public boolean hasNext() {
            return delegate.hasNext();
        }

        @Override
        public Entry<String, Nbt<?>> next() {
            return new DelegateEntry(delegate.next());
        }

        @Override
        public void remove() {
            delegate.remove();
        }
    }

    private static class DelegateEntry implements Entry<String, Nbt<?>> {
        private final Entry<String, RefNbtBase> delegate;

        private DelegateEntry(Entry<String, RefNbtBase> delegate) {
            this.delegate = delegate;
        }

        @Override
        public String getKey() {
            return delegate.getKey();
        }

        @Override
        public Nbt<?> getValue() {
            return Nbt.fromNms(delegate.getValue());
        }

        @Override
        public Nbt<?> setValue(Nbt<?> value) {
            return Nbt.fromNms(delegate.setValue(value.delegate));
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            DelegateEntry that = (DelegateEntry) o;

            return delegate.equals(that.delegate);
        }

        @Override
        public int hashCode() {
            return delegate.hashCode();
        }
    }
}
