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

package com.hypixel.hytale.component;

import java.util.Arrays;
import com.hypixel.hytale.component.data.unknown.UnknownComponents;
import javax.annotation.Nullable;
import com.hypixel.hytale.component.query.ExactArchetypeQuery;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.query.Query;

public class Archetype<ECS_TYPE> implements Query<ECS_TYPE>
{
    @Nonnull
    private static final Archetype EMPTY;
    private final int minIndex;
    private final int count;
    @Nonnull
    private final ComponentType<ECS_TYPE, ?>[] componentTypes;
    @Nonnull
    private final ExactArchetypeQuery<ECS_TYPE> exactQuery;
    
    public static <ECS_TYPE> Archetype<ECS_TYPE> empty() {
        return Archetype.EMPTY;
    }
    
    private Archetype(final int minIndex, final int count, @Nonnull final ComponentType<ECS_TYPE, ?>[] componentTypes) {
        this.exactQuery = new ExactArchetypeQuery<ECS_TYPE>(this);
        this.minIndex = minIndex;
        this.count = count;
        this.componentTypes = componentTypes;
    }
    
    public int getMinIndex() {
        return this.minIndex;
    }
    
    public int count() {
        return this.count;
    }
    
    public int length() {
        return this.componentTypes.length;
    }
    
    @Nullable
    public ComponentType<ECS_TYPE, ?> get(final int index) {
        return this.componentTypes[index];
    }
    
    public boolean isEmpty() {
        return this.componentTypes.length == 0;
    }
    
    public boolean contains(@Nonnull final ComponentType<ECS_TYPE, ?> componentType) {
        final int index = componentType.getIndex();
        return index < this.componentTypes.length && this.componentTypes[index] == componentType;
    }
    
    public boolean contains(@Nonnull final Archetype<ECS_TYPE> archetype) {
        if (this == archetype || archetype.isEmpty()) {
            return true;
        }
        for (int i = archetype.minIndex; i < archetype.componentTypes.length; ++i) {
            final ComponentType<ECS_TYPE, ?> componentType = archetype.componentTypes[i];
            if (componentType != null) {
                if (!this.contains(componentType)) {
                    return false;
                }
            }
        }
        return true;
    }
    
    public void validateComponentType(@Nonnull final ComponentType<ECS_TYPE, ?> componentType) {
        if (!this.contains(componentType)) {
            throw new IllegalArgumentException("ComponentType is not in archetype: " + String.valueOf(componentType) + ", " + String.valueOf(this));
        }
    }
    
    public void validateComponents(@Nonnull final Component<ECS_TYPE>[] components, @Nullable final ComponentType<ECS_TYPE, UnknownComponents<ECS_TYPE>> ignore) {
        for (int len = Math.max(this.componentTypes.length, components.length), index = 0; index < len; ++index) {
            final ComponentType<ECS_TYPE, ?> componentType = (index >= this.componentTypes.length) ? null : this.componentTypes[index];
            final Component<ECS_TYPE> component = (index >= components.length) ? null : components[index];
            if (componentType == null) {
                if (component != null) {
                    if (ignore == null || index != ignore.getIndex()) {
                        throw new IllegalStateException("Invalid component at index " + index + " expected null but found " + String.valueOf(component.getClass()));
                    }
                }
            }
            else {
                final Class<?> typeClass = componentType.getTypeClass();
                if (component == null) {
                    throw new IllegalStateException("Invalid component at index " + index + " expected " + String.valueOf(typeClass) + " but found null");
                }
                final Class<? extends Component> aClass = component.getClass();
                if (!aClass.equals(typeClass)) {
                    throw new IllegalStateException("Invalid component at index " + index + " expected " + String.valueOf(typeClass) + " but found " + String.valueOf(aClass));
                }
            }
        }
    }
    
    public boolean hasSerializableComponents(@Nonnull final ComponentRegistry.Data<ECS_TYPE> data) {
        if (this.isEmpty()) {
            return false;
        }
        if (this.contains(data.getRegistry().getNonSerializedComponentType())) {
            return false;
        }
        for (int index = this.minIndex; index < this.componentTypes.length; ++index) {
            final ComponentType<ECS_TYPE, ?> componentType = this.componentTypes[index];
            if (componentType != null && data.getComponentCodec(componentType) != null) {
                return true;
            }
        }
        return false;
    }
    
    @Nonnull
    public Archetype<ECS_TYPE> getSerializableArchetype(@Nonnull final ComponentRegistry.Data<ECS_TYPE> data) {
        if (this.isEmpty()) {
            return Archetype.EMPTY;
        }
        if (this.contains(data.getRegistry().getNonSerializedComponentType())) {
            return Archetype.EMPTY;
        }
        int lastSerializableIndex = this.componentTypes.length - 1;
        for (int index = this.componentTypes.length - 1; index >= this.minIndex; --index) {
            final ComponentType<ECS_TYPE, ?> componentType = this.componentTypes[index];
            if (componentType != null && data.getComponentCodec(componentType) != null) {
                lastSerializableIndex = index;
                break;
            }
        }
        if (lastSerializableIndex < this.minIndex) {
            return Archetype.EMPTY;
        }
        final ComponentType<ECS_TYPE, ?>[] serializableComponentTypes = new ComponentType[lastSerializableIndex + 1];
        int index2;
        for (int serializableMinIndex = index2 = this.minIndex; index2 < serializableComponentTypes.length; ++index2) {
            final ComponentType<ECS_TYPE, ?> componentType2 = this.componentTypes[index2];
            if (componentType2 != null && data.getComponentCodec(componentType2) != null) {
                serializableMinIndex = Math.min(serializableMinIndex, index2);
                serializableComponentTypes[index2] = componentType2;
            }
        }
        return new Archetype<ECS_TYPE>(this.minIndex, serializableComponentTypes.length, serializableComponentTypes);
    }
    
    @Nonnull
    public ExactArchetypeQuery<ECS_TYPE> asExactQuery() {
        return this.exactQuery;
    }
    
    @Nonnull
    public static <ECS_TYPE> Archetype<ECS_TYPE> of(@Nonnull final ComponentType<ECS_TYPE, ?> componentTypes) {
        final int index = componentTypes.getIndex();
        final ComponentType<ECS_TYPE, ?>[] arr = new ComponentType[index + 1];
        arr[index] = componentTypes;
        return new Archetype<ECS_TYPE>(index, 1, arr);
    }
    
    @SafeVarargs
    public static <ECS_TYPE> Archetype<ECS_TYPE> of(@Nonnull final ComponentType<ECS_TYPE, ?>... componentTypes) {
        if (componentTypes.length == 0) {
            return Archetype.EMPTY;
        }
        final ComponentRegistry<ECS_TYPE> registry = componentTypes[0].getRegistry();
        int minIndex = Integer.MAX_VALUE;
        int maxIndex = Integer.MIN_VALUE;
        for (int i = 0; i < componentTypes.length; ++i) {
            componentTypes[i].validateRegistry(registry);
            final int index = componentTypes[i].getIndex();
            if (index < minIndex) {
                minIndex = index;
            }
            if (index > maxIndex) {
                maxIndex = index;
            }
            for (int n = i + 1; n < componentTypes.length; ++n) {
                if (componentTypes[i] == componentTypes[n]) {
                    throw new IllegalArgumentException("ComponentType provided multiple times! " + Arrays.toString(componentTypes));
                }
            }
        }
        final ComponentType<ECS_TYPE, ?>[] arr = new ComponentType[maxIndex + 1];
        for (final ComponentType<ECS_TYPE, ?> componentType : componentTypes) {
            arr[componentType.getIndex()] = componentType;
        }
        return new Archetype<ECS_TYPE>(minIndex, componentTypes.length, arr);
    }
    
    @Nonnull
    public static <ECS_TYPE, T extends Component<ECS_TYPE>> Archetype<ECS_TYPE> add(@Nonnull final Archetype<ECS_TYPE> archetype, @Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        if (archetype.isEmpty()) {
            return of(componentType);
        }
        if (archetype.contains(componentType)) {
            throw new IllegalArgumentException("ComponentType is already in Archetype! " + String.valueOf(archetype) + ", " + String.valueOf(componentType));
        }
        archetype.validateRegistry(componentType.getRegistry());
        final int index = componentType.getIndex();
        final int minIndex = Math.min(index, archetype.minIndex);
        final int newLength = Math.max(index + 1, archetype.componentTypes.length);
        final ComponentType<ECS_TYPE, ?>[] arr = Arrays.copyOf(archetype.componentTypes, newLength);
        arr[index] = componentType;
        return new Archetype<ECS_TYPE>(minIndex, archetype.count + 1, arr);
    }
    
    public static <ECS_TYPE, T extends Component<ECS_TYPE>> Archetype<ECS_TYPE> remove(@Nonnull final Archetype<ECS_TYPE> archetype, @Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        if (archetype.isEmpty()) {
            throw new IllegalArgumentException("Archetype is already empty!");
        }
        if (!archetype.contains(componentType)) {
            throw new IllegalArgumentException("Archetype doesn't contain ComponentType! " + String.valueOf(archetype) + ", " + String.valueOf(componentType));
        }
        final int oldLength = archetype.componentTypes.length;
        final int oldMinIndex = archetype.minIndex;
        final int oldMaxIndex = oldLength - 1;
        if (oldMinIndex == oldMaxIndex) {
            return Archetype.EMPTY;
        }
        final int newCount = archetype.count - 1;
        final int index = componentType.getIndex();
        if (index == oldMaxIndex) {
            int maxIndex;
            for (maxIndex = index - 1; maxIndex > oldMinIndex && archetype.componentTypes[maxIndex] == null; --maxIndex) {}
            return new Archetype<ECS_TYPE>(oldMinIndex, newCount, Arrays.copyOf(archetype.componentTypes, maxIndex + 1));
        }
        final ComponentType<ECS_TYPE, ?>[] arr = Arrays.copyOf(archetype.componentTypes, oldLength);
        arr[index] = null;
        if (index == oldMinIndex) {
            int minIndex;
            for (minIndex = index + 1; minIndex < oldLength && arr[minIndex] == null; ++minIndex) {}
            return new Archetype<ECS_TYPE>(minIndex, newCount, arr);
        }
        return new Archetype<ECS_TYPE>(oldMinIndex, newCount, arr);
    }
    
    @Override
    public boolean test(@Nonnull final Archetype<ECS_TYPE> archetype) {
        return archetype.contains(this);
    }
    
    @Override
    public boolean requiresComponentType(@Nonnull final ComponentType<ECS_TYPE, ?> componentType) {
        return this.contains(componentType);
    }
    
    @Override
    public void validateRegistry(@Nonnull final ComponentRegistry<ECS_TYPE> registry) {
        if (this.isEmpty()) {
            return;
        }
        this.componentTypes[this.minIndex].validateRegistry(registry);
    }
    
    @Override
    public void validate() {
        for (int i = this.minIndex; i < this.componentTypes.length; ++i) {
            final ComponentType<ECS_TYPE, ?> componentType = this.componentTypes[i];
            if (componentType != null) {
                componentType.validate();
            }
        }
    }
    
    @Override
    public boolean equals(@Nullable final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        final Archetype<?> archetype = (Archetype<?>)o;
        return Arrays.equals(this.componentTypes, archetype.componentTypes);
    }
    
    @Override
    public int hashCode() {
        return Arrays.hashCode(this.componentTypes);
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "Archetype{componentTypes=" + Arrays.toString(this.componentTypes);
    }
    
    static {
        EMPTY = new Archetype(0, 0, ComponentType.EMPTY_ARRAY);
    }
}
