package p.studio.utilities.structures;

import java.util.*;
import java.util.stream.Stream;

public interface MutableList<T> extends ReadOnlyCollection<T>, List<T> {
    static <T> MutableList<T> create() {
        return wrapInternal(new ArrayList<>());
    }

    static <T> MutableList<T> withCapacity(final int initialCapacity) {
        return wrapInternal(new ArrayList<>(initialCapacity));
    }

    static <T> MutableList<T> wrap(final Collection<T> c) {
        if (c == null || c.isEmpty()) {
            return MutableList.create();
        }
        return wrapInternal(new ArrayList<>(c));
    }

    @SafeVarargs
    static <T> MutableList<T> from(final T... values) {
        if (values == null || values.length == 0) {
            return MutableList.create();
        }
        return MutableList.wrap(Stream.of(values).toList());
    }

    private static <T> MutableList<T> wrapInternal(final List<T> list) {
        return new MutableList<>() {
            @Override
            public List<T> asList() {
                return list;
            }

            @Override
            public boolean equals(final Object o) {
                if (o == this) return true;
                if (o instanceof MutableList<?> other) {
                    return elementsEqual(other);
                }
                if (o instanceof ReadOnlyList<?> otherReadOnly) {
                    return elementsEqual(otherReadOnly);
                }
                return false;
            }

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

    List<T> asList();

    @Override
    default Collection<T> asCollection() {
        return asList();
    }

    @Override
    default int size() {
        return asList().size();
    }

    @Override
    default boolean isEmpty() {
        return asList().isEmpty();
    }

    @Override
    default boolean contains(final Object o) {
        return asList().contains(o);
    }

    @Override
    default java.util.Iterator<T> iterator() {
        return asList().iterator();
    }

    @Override
    default java.util.Spliterator<T> spliterator() {
        return asList().spliterator();
    }

    @Override
    default Object[] toArray() {
        return asList().toArray();
    }

    @Override
    default <T1> T1[] toArray(final T1[] a) {
        return asList().toArray(a);
    }

    @Override
    default boolean add(final T t) {
        return asList().add(t);
    }

    @Override
    default boolean remove(final Object o) {
        return asList().remove(o);
    }

    @Override
    default boolean containsAll(final Collection<?> c) {
        return asList().containsAll(c);
    }

    /**
     * Appends all functions from the given file using, similar to
     * {@link java.util.List#addAll(java.util.Collection)} semantics.
     */
    default boolean addAll(final ReadOnlyCollection<? extends T> c) {
        if (ReadOnlyCollection.isEmpty(c)) {
            return false;
        }
        return asList().addAll(c.asCollection());
    }

    @Override
    default boolean addAll(final Collection<? extends T> c) {
        return asList().addAll(c);
    }

    @Override
    default boolean addAll(final int index, final Collection<? extends T> c) {
        return asList().addAll(index, c);
    }

    @Override
    default boolean removeAll(final Collection<?> c) {
        return asList().removeAll(c);
    }

    @Override
    default boolean retainAll(final Collection<?> c) {
        return asList().retainAll(c);
    }

    @Override
    default void clear() {
        asList().clear();
    }

    @Override
    default T get(final int index) {
        return asList().get(index);
    }

    @Override
    default T set(final int index, final T element) {
        return asList().set(index, element);
    }

    @Override
    default void add(final int index, final T element) {
        asList().add(index, element);
    }

    @Override
    default T remove(final int index) {
        return asList().remove(index);
    }

    @Override
    default int indexOf(final Object o) {
        return asList().indexOf(o);
    }

    @Override
    default int lastIndexOf(final Object o) {
        return asList().lastIndexOf(o);
    }

    @Override
    default ListIterator<T> listIterator() {
        return asList().listIterator();
    }

    @Override
    default ListIterator<T> listIterator(final int index) {
        return asList().listIterator(index);
    }

    @Override
    default List<T> subList(final int fromIndex, final int toIndex) {
        return asList().subList(fromIndex, toIndex);
    }

    @Override
    default Stream<T> stream() {
        return asList().stream();
    }

    default T getFirst() {
        return asList().getFirst();
    }

    default T getLast() {
        return asList().getLast();
    }

    default MutableList<T> mutableSubList(final int fromIndex, final int toIndex) {
        return MutableList.wrap(subList(fromIndex, toIndex));
    }

    default ReadOnlyList<T> toReadOnlyList() {
        return ReadOnlyList.wrap(asList());
    }

    default ReadOnlyList<T> immutableSubList(final int fromIndex, final int toIndex) {
        return ReadOnlyList.wrap(subList(fromIndex, toIndex));
    }

    default ReadOnlyList<T> immutableSort(final java.util.Comparator<? super T> c) {
        if (c == null) {
            return ReadOnlyList.wrap(asList().stream().sorted().toList());
        }
        return ReadOnlyList.wrap(asList().stream().sorted(c).toList());
    }

    default ReadOnlyList<T> invert() {
        final var inverted = new ArrayList<>(asList());
        java.util.Collections.reverse(inverted);
        return ReadOnlyList.wrap(inverted);
    }

    default boolean elementsEqual(final ReadOnlyList<?> other) {
        if (other == null || other.size() != size()) {
            return false;
        }
        for (int i = 0; i < size(); i++) {
            if (!Objects.equals(get(i), other.get(i))) {
                return false;
            }
        }
        return true;
    }

    default boolean elementsEqual(final MutableList<?> other) {
        return elementsEqual(other == null ? null : other.toReadOnlyList());
    }
}
