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

package com.hypixel.hytale.component;

import java.util.Objects;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.Collections;
import java.util.Iterator;
import com.hypixel.hytale.component.data.unknown.TempUnknownComponent;
import org.bson.BsonValue;
import com.hypixel.hytale.codec.ExtraInfo;
import org.bson.BsonDocument;
import java.util.Map;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.component.data.change.DataChange;
import com.hypixel.hytale.component.data.change.ComponentChange;
import com.hypixel.hytale.component.data.unknown.UnknownComponents;
import java.util.Arrays;
import javax.annotation.Nonnull;
import java.util.concurrent.locks.StampedLock;
import javax.annotation.Nullable;

public class Holder<ECS_TYPE>
{
    private static final Holder<?>[] EMPTY_ARRAY;
    @Nullable
    private final ComponentRegistry<ECS_TYPE> registry;
    private final StampedLock lock;
    @Nullable
    private Archetype<ECS_TYPE> archetype;
    @Nullable
    private Component<ECS_TYPE>[] components;
    private boolean ensureValidComponents;
    
    public static <T> Holder<T>[] emptyArray() {
        return (Holder<T>[])Holder.EMPTY_ARRAY;
    }
    
    Holder() {
        this.lock = new StampedLock();
        this.ensureValidComponents = true;
        this.registry = null;
    }
    
    Holder(@Nonnull final ComponentRegistry<ECS_TYPE> registry) {
        this.lock = new StampedLock();
        this.ensureValidComponents = true;
        this.registry = registry;
        this.archetype = Archetype.empty();
        this.components = Component.EMPTY_ARRAY;
    }
    
    Holder(@Nonnull final ComponentRegistry<ECS_TYPE> registry, @Nonnull final Archetype<ECS_TYPE> archetype, @Nonnull final Component<ECS_TYPE>[] components) {
        this.lock = new StampedLock();
        this.ensureValidComponents = true;
        this.registry = registry;
        this.init(archetype, components);
    }
    
    @Nonnull
    public Component<ECS_TYPE>[] ensureComponentsSize(final int size) {
        final long stamp = this.lock.writeLock();
        try {
            if (this.components == null) {
                return this.components = new Component[size];
            }
            if (this.components.length < size) {
                this.components = Arrays.copyOf(this.components, size);
            }
            return this.components;
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    public void init(@Nonnull final Archetype<ECS_TYPE> archetype, @Nonnull final Component<ECS_TYPE>[] components) {
        archetype.validate();
        archetype.validateComponents(components, null);
        final long stamp = this.lock.writeLock();
        try {
            this.archetype = archetype;
            this.components = components;
            this.ensureValidComponents = true;
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    public void _internal_init(@Nonnull final Archetype<ECS_TYPE> archetype, @Nonnull final Component<ECS_TYPE>[] components, @Nonnull final ComponentType<ECS_TYPE, UnknownComponents<ECS_TYPE>> unknownComponentType) {
        archetype.validateComponents(components, unknownComponentType);
        final long stamp = this.lock.writeLock();
        try {
            this.archetype = archetype;
            this.components = components;
            this.ensureValidComponents = false;
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    @Nullable
    public Archetype<ECS_TYPE> getArchetype() {
        return this.archetype;
    }
    
    public <T extends Component<ECS_TYPE>> void ensureComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        assert this.archetype != null;
        assert this.registry != null;
        if (this.ensureValidComponents) {
            componentType.validate();
        }
        final long stamp = this.lock.writeLock();
        try {
            if (this.archetype.contains(componentType)) {
                return;
            }
            final T component = this.registry.createComponent(componentType);
            this.addComponent0(componentType, component);
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    @Nonnull
    public <T extends Component<ECS_TYPE>> T ensureAndGetComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        this.ensureComponent(componentType);
        return (T)this.getComponent((ComponentType<ECS_TYPE, Component>)componentType);
    }
    
    public <T extends Component<ECS_TYPE>> void addComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType, @Nonnull final T component) {
        assert this.archetype != null;
        final long stamp = this.lock.writeLock();
        try {
            if (this.ensureValidComponents) {
                componentType.validate();
            }
            if (this.archetype.contains(componentType)) {
                throw new IllegalArgumentException("Entity contains component type: " + String.valueOf(componentType));
            }
            this.addComponent0((ComponentType<ECS_TYPE, Component>)componentType, component);
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    private <T extends Component<ECS_TYPE>> void addComponent0(@Nonnull final ComponentType<ECS_TYPE, T> componentType, @Nonnull final T component) {
        assert this.archetype != null;
        assert this.components != null;
        this.archetype = Archetype.add(this.archetype, componentType);
        final int newLength = this.archetype.length();
        if (this.components.length < newLength) {
            this.components = Arrays.copyOf(this.components, newLength);
        }
        this.components[componentType.getIndex()] = component;
    }
    
    public <T extends Component<ECS_TYPE>> void replaceComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType, @Nonnull final T component) {
        assert this.archetype != null;
        assert this.components != null;
        final long stamp = this.lock.writeLock();
        try {
            if (this.ensureValidComponents) {
                componentType.validate();
            }
            this.archetype.validateComponentType(componentType);
            this.components[componentType.getIndex()] = component;
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    public <T extends Component<ECS_TYPE>> void putComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType, @Nonnull final T component) {
        if (this.getComponent(componentType) != null) {
            this.replaceComponent((ComponentType<ECS_TYPE, Component>)componentType, component);
        }
        else {
            this.addComponent((ComponentType<ECS_TYPE, Component>)componentType, component);
        }
    }
    
    @Nullable
    public <T extends Component<ECS_TYPE>> T getComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        assert this.archetype != null;
        assert this.components != null;
        final long stamp = this.lock.readLock();
        try {
            if (this.ensureValidComponents) {
                componentType.validate();
            }
            if (!this.archetype.contains(componentType)) {
                return null;
            }
            return (T)this.components[componentType.getIndex()];
        }
        finally {
            this.lock.unlockRead(stamp);
        }
    }
    
    public <T extends Component<ECS_TYPE>> void removeComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        assert this.archetype != null;
        assert this.components != null;
        final long stamp = this.lock.writeLock();
        try {
            if (this.ensureValidComponents) {
                componentType.validate();
            }
            this.archetype.validateComponentType(componentType);
            this.archetype = Archetype.remove(this.archetype, componentType);
            this.components[componentType.getIndex()] = null;
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    public <T extends Component<ECS_TYPE>> boolean tryRemoveComponent(@Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        if (this.getComponent(componentType) == null) {
            return false;
        }
        this.removeComponent(componentType);
        return true;
    }
    
    public boolean hasSerializableComponents(@Nonnull final ComponentRegistry.Data<ECS_TYPE> data) {
        assert this.archetype != null;
        return this.archetype.hasSerializableComponents(data);
    }
    
    public void updateData(@Nonnull final ComponentRegistry.Data<ECS_TYPE> oldData, @Nonnull final ComponentRegistry.Data<ECS_TYPE> newData) {
        assert this.archetype != null;
        assert this.components != null;
        assert this.registry != null;
        final long stamp = this.lock.writeLock();
        try {
            if (this.archetype.isEmpty()) {
                return;
            }
            final ComponentType<ECS_TYPE, UnknownComponents<ECS_TYPE>> unknownComponentType = this.registry.getUnknownComponentType();
            for (int i = 0; i < newData.getDataChangeCount(); ++i) {
                final DataChange dataChange = newData.getDataChange(i);
                if (dataChange instanceof ComponentChange) {
                    final ComponentChange<ECS_TYPE, ? extends Component<ECS_TYPE>> componentChange = (ComponentChange<ECS_TYPE, ? extends Component<ECS_TYPE>>)dataChange;
                    final ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = componentChange.getComponentType();
                    switch (componentChange.getType()) {
                        case REGISTERED: {
                            assert this.archetype != null;
                            if (this.archetype.contains(componentType)) {
                                break;
                            }
                            if (!this.archetype.contains(unknownComponentType)) {
                                break;
                            }
                            final String componentId = newData.getComponentId(componentType);
                            final Codec<Component<ECS_TYPE>> componentCodec = newData.getComponentCodec(componentType);
                            if (componentCodec != null) {
                                final UnknownComponents<ECS_TYPE> unknownComponents = (UnknownComponents)this.components[unknownComponentType.getIndex()];
                                assert unknownComponents != null;
                                final Component<ECS_TYPE> component = unknownComponents.removeComponent(componentId, componentCodec);
                                if (component != null) {
                                    this.addComponent0((ComponentType<ECS_TYPE, Component<ECS_TYPE>>)componentType, component);
                                }
                            }
                            break;
                        }
                        case UNREGISTERED: {
                            assert this.archetype != null;
                            if (!this.archetype.contains(componentType)) {
                                break;
                            }
                            final String componentId = oldData.getComponentId(componentType);
                            final Codec<Component<ECS_TYPE>> componentCodec = oldData.getComponentCodec(componentType);
                            if (componentCodec != null) {
                                UnknownComponents<ECS_TYPE> unknownComponents;
                                if (this.archetype.contains(unknownComponentType)) {
                                    unknownComponents = (UnknownComponents)this.components[unknownComponentType.getIndex()];
                                    assert unknownComponents != null;
                                }
                                else {
                                    unknownComponents = new UnknownComponents<ECS_TYPE>();
                                    this.addComponent0(unknownComponentType, unknownComponents);
                                }
                                final Component<ECS_TYPE> component = this.components[componentType.getIndex()];
                                unknownComponents.addComponent(componentId, component, componentCodec);
                            }
                            this.archetype = Archetype.remove(this.archetype, componentType);
                            this.components[componentType.getIndex()] = null;
                            break;
                        }
                    }
                }
            }
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    @Nonnull
    public Holder<ECS_TYPE> clone() {
        assert this.archetype != null;
        assert this.components != null;
        assert this.registry != null;
        final long stamp = this.lock.readLock();
        try {
            final Component<ECS_TYPE>[] componentsClone = new Component[this.components.length];
            for (int i = 0; i < this.components.length; ++i) {
                final Component<ECS_TYPE> component = this.components[i];
                if (component != null) {
                    componentsClone[i] = component.clone();
                }
            }
            return this.registry.newHolder(this.archetype, componentsClone);
        }
        finally {
            this.lock.unlockRead(stamp);
        }
    }
    
    public Holder<ECS_TYPE> cloneSerializable(@Nonnull final ComponentRegistry.Data<ECS_TYPE> data) {
        assert this.archetype != null;
        assert this.components != null;
        assert this.registry != null;
        final long stamp = this.lock.readLock();
        try {
            final Archetype<ECS_TYPE> serializableArchetype = this.archetype.getSerializableArchetype(data);
            final Component<ECS_TYPE>[] componentsClone = new Component[serializableArchetype.length()];
            for (int i = serializableArchetype.getMinIndex(); i < serializableArchetype.length(); ++i) {
                final ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>)serializableArchetype.get(i);
                if (componentType != null) {
                    componentsClone[i] = this.components[i].cloneSerializable();
                }
            }
            return this.registry.newHolder(serializableArchetype, componentsClone);
        }
        finally {
            this.lock.unlockRead(stamp);
        }
    }
    
    void loadComponentsMap(@Nonnull final ComponentRegistry.Data<ECS_TYPE> data, @Nonnull final Map<String, Component<ECS_TYPE>> map) {
        assert this.components != null;
        final long stamp = this.lock.writeLock();
        try {
            ComponentType<ECS_TYPE, ?>[] componentTypes = new ComponentType[map.size()];
            int i = 0;
            final ComponentType<ECS_TYPE, UnknownComponents<ECS_TYPE>> unknownComponentType = data.getRegistry().getUnknownComponentType();
            UnknownComponents<ECS_TYPE> unknownComponents = map.remove("Unknown");
            if (unknownComponents != null) {
                for (final Map.Entry<String, BsonDocument> e : unknownComponents.getUnknownComponents().entrySet()) {
                    final ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> type = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>)data.getComponentType(e.getKey());
                    if (type == null) {
                        continue;
                    }
                    if (map.containsKey(e.getKey())) {
                        continue;
                    }
                    final Codec<Component<ECS_TYPE>> codec = data.getComponentCodec(type);
                    final ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get();
                    final Component<ECS_TYPE> decodedComponent = codec.decode(e.getValue(), extraInfo);
                    extraInfo.getValidationResults().logOrThrowValidatorExceptions(UnknownComponents.LOGGER);
                    if (componentTypes.length <= i) {
                        componentTypes = Arrays.copyOf(componentTypes, i + 1);
                    }
                    componentTypes[i++] = type;
                    final int index = type.getIndex();
                    if (this.components.length <= index) {
                        this.components = Arrays.copyOf(this.components, index + 1);
                    }
                    this.components[index] = decodedComponent;
                }
                if (componentTypes.length <= i) {
                    componentTypes = Arrays.copyOf(componentTypes, i + 1);
                }
                componentTypes[i++] = unknownComponentType;
                final int index2 = unknownComponentType.getIndex();
                if (this.components.length <= index2) {
                    this.components = Arrays.copyOf(this.components, index2 + 1);
                }
                this.components[index2] = unknownComponents;
            }
            for (final Map.Entry<String, Component<ECS_TYPE>> entry : map.entrySet()) {
                final Component<ECS_TYPE> component = entry.getValue();
                if (component instanceof final TempUnknownComponent tempUnknownComponent) {
                    if (unknownComponents == null) {
                        unknownComponents = new UnknownComponents<ECS_TYPE>();
                        if (componentTypes.length <= i) {
                            componentTypes = Arrays.copyOf(componentTypes, i + 1);
                        }
                        componentTypes[i++] = unknownComponentType;
                        final int index3 = unknownComponentType.getIndex();
                        if (this.components.length <= index3) {
                            this.components = Arrays.copyOf(this.components, index3 + 1);
                        }
                        this.components[index3] = unknownComponents;
                    }
                    unknownComponents.addComponent(entry.getKey(), tempUnknownComponent);
                }
                else {
                    final ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>)data.getComponentType(entry.getKey());
                    if (componentTypes.length <= i) {
                        componentTypes = Arrays.copyOf(componentTypes, i + 1);
                    }
                    componentTypes[i++] = componentType;
                    final int index3 = componentType.getIndex();
                    if (this.components.length <= index3) {
                        this.components = Arrays.copyOf(this.components, index3 + 1);
                    }
                    this.components[index3] = component;
                }
            }
            this.archetype = Archetype.of((componentTypes.length == i) ? componentTypes : Arrays.copyOf(componentTypes, i));
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    @Nonnull
    Map<String, Component<ECS_TYPE>> createComponentsMap(@Nonnull final ComponentRegistry.Data<ECS_TYPE> data) {
        assert this.archetype != null;
        assert this.components != null;
        final long stamp = this.lock.readLock();
        try {
            if (this.archetype.isEmpty()) {
                return Collections.emptyMap();
            }
            final ComponentRegistry<ECS_TYPE> registry = data.getRegistry();
            final ComponentType<ECS_TYPE, UnknownComponents<ECS_TYPE>> unknownComponentType = registry.getUnknownComponentType();
            final Map<String, Component<ECS_TYPE>> map = new Object2ObjectOpenHashMap<String, Component<ECS_TYPE>>(this.archetype.length());
            for (int i = this.archetype.getMinIndex(); i < this.archetype.length(); ++i) {
                final ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>> componentType = (ComponentType<ECS_TYPE, ? extends Component<ECS_TYPE>>)this.archetype.get(i);
                if (componentType != null) {
                    if (data.getComponentCodec(componentType) != null) {
                        if (componentType == unknownComponentType) {
                            final UnknownComponents<ECS_TYPE> unknownComponents = (UnknownComponents)this.components[componentType.getIndex()];
                            for (final Map.Entry<String, BsonDocument> entry : unknownComponents.getUnknownComponents().entrySet()) {
                                map.putIfAbsent(entry.getKey(), new TempUnknownComponent<ECS_TYPE>(entry.getValue()));
                            }
                        }
                        else {
                            map.put(data.getComponentId(componentType), this.components[componentType.getIndex()]);
                        }
                    }
                }
            }
            return map;
        }
        finally {
            this.lock.unlockRead(stamp);
        }
    }
    
    @Override
    public boolean equals(@Nullable final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        final Holder<?> that = (Holder<?>)o;
        final long stamp = this.lock.readLock();
        final long thatStamp = that.lock.readLock();
        try {
            return Objects.equals(this.archetype, that.archetype) && Arrays.equals(this.components, that.components);
        }
        finally {
            that.lock.unlockRead(thatStamp);
            this.lock.unlockRead(stamp);
        }
    }
    
    @Override
    public int hashCode() {
        final long stamp = this.lock.readLock();
        try {
            int result = (this.archetype != null) ? this.archetype.hashCode() : 0;
            result = 31 * result + Arrays.hashCode(this.components);
            return result;
        }
        finally {
            this.lock.unlockRead(stamp);
        }
    }
    
    @Nonnull
    @Override
    public String toString() {
        final long stamp = this.lock.readLock();
        try {
            return "EntityHolder{archetype=" + String.valueOf(this.archetype) + ", components=" + Arrays.toString(this.components);
        }
        finally {
            this.lock.unlockRead(stamp);
        }
    }
    
    static {
        EMPTY_ARRAY = new Holder[0];
    }
}
