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

package com.hypixel.hytale.server.core.entity.entities.player.windows;

import java.util.UUID;
import java.util.Map;
import java.util.Iterator;
import com.hypixel.hytale.protocol.packets.window.CloseWindow;
import com.hypixel.hytale.protocol.Packet;
import java.util.Collection;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.event.EventPriority;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import it.unimi.dsi.fastutil.objects.ObjectList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import com.hypixel.hytale.protocol.packets.window.OpenWindow;
import javax.annotation.Nullable;
import com.hypixel.hytale.protocol.ExtraResources;
import com.hypixel.hytale.protocol.InventorySection;
import java.util.logging.Level;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.protocol.packets.window.UpdateWindow;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.event.EventRegistration;
import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nonnull;
import com.hypixel.hytale.logger.HytaleLogger;

public class WindowManager
{
    @Nonnull
    private static final HytaleLogger LOGGER;
    private final AtomicInteger windowId;
    @Nonnull
    private final Int2ObjectConcurrentHashMap<Window> windows;
    @Nonnull
    private final Int2ObjectConcurrentHashMap<EventRegistration<?, ?>> windowChangeEvents;
    private PlayerRef playerRef;
    
    public WindowManager() {
        this.windowId = new AtomicInteger(1);
        this.windows = new Int2ObjectConcurrentHashMap<Window>();
        this.windowChangeEvents = new Int2ObjectConcurrentHashMap<EventRegistration<?, ?>>();
    }
    
    public void init(@Nonnull final PlayerRef playerRef) {
        this.playerRef = playerRef;
    }
    
    @Nullable
    public UpdateWindow clientOpenWindow(@Nonnull final Ref<EntityStore> ref, @Nonnull final Window window, @Nonnull final Store<EntityStore> store) {
        if (!Window.CLIENT_REQUESTABLE_WINDOW_TYPES.containsKey(window.getType())) {
            throw new IllegalArgumentException("Client opened window must be registered in Window.CLIENT_REQUESTABLE_WINDOW_TYPES but got: " + String.valueOf(window.getType()));
        }
        final int id = 0;
        final Window oldWindow = this.windows.remove(0);
        if (oldWindow != null) {
            if (oldWindow instanceof ItemContainerWindow) {
                this.windowChangeEvents.remove(oldWindow.getId()).unregister();
            }
            oldWindow.onClose(ref, store);
            WindowManager.LOGGER.at(Level.FINE).log("%s close window %s with id %s", this.playerRef.getUuid(), oldWindow.getType(), 0);
        }
        this.setWindow0(0, window);
        if (!window.onOpen(ref, store)) {
            this.closeWindow(ref, 0, store);
            window.setId(-1);
            return null;
        }
        if (!window.consumeIsDirty()) {
            return null;
        }
        InventorySection section = null;
        if (window instanceof final ItemContainerWindow itemContainerWindow) {
            section = itemContainerWindow.getItemContainer().toPacket();
        }
        ExtraResources extraResources = null;
        if (window instanceof final MaterialContainerWindow materialContainerWindow) {
            extraResources = materialContainerWindow.getExtraResourcesSection().toPacket();
        }
        return new UpdateWindow(0, window.getData().toString(), section, extraResources);
    }
    
    @Nullable
    public OpenWindow openWindow(@Nonnull final Ref<EntityStore> ref, @Nonnull final Window window, @Nonnull final Store<EntityStore> store) {
        final int id = this.windowId.getAndUpdate(operand -> (++operand > 0) ? operand : 1);
        this.setWindow(id, window);
        if (!window.onOpen(ref, store)) {
            this.closeWindow(ref, id, store);
            window.setId(-1);
            return null;
        }
        window.consumeIsDirty();
        WindowManager.LOGGER.at(Level.FINE).log("%s opened window %s with id %s and data %s", this.playerRef.getUuid(), window.getType(), id, window.getData());
        InventorySection section = null;
        if (window instanceof final ItemContainerWindow itemContainerWindow) {
            section = itemContainerWindow.getItemContainer().toPacket();
        }
        ExtraResources extraResources = null;
        if (window instanceof final MaterialContainerWindow materialContainerWindow) {
            extraResources = materialContainerWindow.getExtraResourcesSection().toPacket();
        }
        return new OpenWindow(id, window.getType(), window.getData().toString(), section, extraResources);
    }
    
    @Nullable
    public List<OpenWindow> openWindows(@Nonnull final Ref<EntityStore> ref, @Nonnull final Store<EntityStore> store, @Nonnull final Window... windows) {
        final ObjectList<OpenWindow> packets = new ObjectArrayList<OpenWindow>();
        for (final Window window : windows) {
            final OpenWindow packet = this.openWindow(ref, window, store);
            if (packet == null) {
                for (final OpenWindow addedPacket : packets) {
                    this.closeWindow(ref, addedPacket.id, store);
                }
                return null;
            }
            packets.add(packet);
        }
        return packets;
    }
    
    public void setWindow(final int id, @Nonnull final Window window) {
        if (id >= this.windowId.get()) {
            throw new IllegalArgumentException("id is outside of the range, use addWindow");
        }
        if (id == 0 || id == -1) {
            throw new IllegalArgumentException("id is invalid, can't be 0 or -1");
        }
        this.setWindow0(id, window);
    }
    
    private void setWindow0(final int id, @Nonnull final Window window) {
        if (this.windows.putIfAbsent(id, window) != null) {
            throw new IllegalArgumentException("Window " + id + " already exists");
        }
        window.setId(id);
        window.init(this.playerRef, this);
        if (window instanceof final ItemContainerWindow itemContainerWindow) {
            final ItemContainer itemContainer = itemContainerWindow.getItemContainer();
            this.windowChangeEvents.put(id, itemContainer.registerChangeEvent(EventPriority.LAST, e -> this.markWindowChanged(id)));
        }
    }
    
    @Nullable
    public Window getWindow(final int id) {
        if (id == -1) {
            throw new IllegalArgumentException("Window id -1 is invalid!");
        }
        return this.windows.get(id);
    }
    
    @Nonnull
    public List<Window> getWindows() {
        return new ObjectArrayList<Window>(this.windows.values());
    }
    
    public void updateWindow(@Nonnull final Window window) {
        InventorySection section = null;
        if (window instanceof final ItemContainerWindow itemContainerWindow) {
            section = itemContainerWindow.getItemContainer().toPacket();
        }
        ExtraResources extraResources = null;
        if (window instanceof final MaterialContainerWindow materialContainerWindow) {
            if (!materialContainerWindow.isValid()) {
                extraResources = materialContainerWindow.getExtraResourcesSection().toPacket();
            }
        }
        this.playerRef.getPacketHandler().writeNoCache(new UpdateWindow(window.getId(), window.getData().toString(), section, extraResources));
        window.consumeNeedRebuild();
        WindowManager.LOGGER.at(Level.FINER).log("%s update window %s with id %s and data %s", this.playerRef.getUuid(), window.getType(), window.getId(), window.getData());
    }
    
    @Nonnull
    public Window closeWindow(@Nonnull final Ref<EntityStore> ref, final int id, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (id == -1) {
            throw new IllegalArgumentException("Window id -1 is invalid!");
        }
        final PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType());
        assert playerRefComponent != null;
        playerRefComponent.getPacketHandler().writeNoCache(new CloseWindow(id));
        final Window window = this.windows.remove(id);
        if (window instanceof ItemContainerWindow) {
            this.windowChangeEvents.remove(window.getId()).unregister();
        }
        if (window == null) {
            throw new IllegalStateException("Window id " + id + " is invalid!");
        }
        window.onClose(ref, componentAccessor);
        WindowManager.LOGGER.at(Level.FINE).log("%s close window %s with id %s", this.playerRef.getUuid(), window.getType(), id);
        return window;
    }
    
    public void closeAllWindows(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        for (final Window window : this.windows.values()) {
            window.close(ref, componentAccessor);
        }
    }
    
    public void markWindowChanged(final int id) {
        final Window window = this.getWindow(id);
        if (window != null) {
            window.invalidate();
        }
    }
    
    public void updateWindows() {
        this.windows.forEach((id, window, _windowManager) -> {
            if (window.consumeIsDirty()) {
                _windowManager.updateWindow(window);
            }
        }, this);
    }
    
    public void validateWindows(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        for (final Window value : this.windows.values()) {
            if (value instanceof final ValidatedWindow validatedWindow) {
                if (validatedWindow.validate(ref, componentAccessor)) {
                    continue;
                }
                value.close(ref, componentAccessor);
            }
        }
    }
    
    public static <W extends Window> void closeAndRemoveAll(@Nonnull final Map<UUID, W> windows) {
        final Iterator<W> iterator = windows.values().iterator();
        while (iterator.hasNext()) {
            final W window = iterator.next();
            final PlayerRef playerRef = window.getPlayerRef();
            if (playerRef != null) {
                final Ref<EntityStore> ref = playerRef.getReference();
                if (ref != null && ref.isValid()) {
                    final Store<EntityStore> store = ref.getStore();
                    window.close(ref, store);
                }
            }
            iterator.remove();
        }
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "WindowManager{windowId=" + String.valueOf(this.windowId) + ", windows=" + String.valueOf(this.windows);
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
}
