package org.xbib.datastructures.immutable;

import java.util.EnumMap;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.function.BiConsumer;
import static org.xbib.datastructures.immutable.UnmodifiableIterator.unmodifiableIterator;

/**
 * Implementation of {@link ImmutableMap} backed by a non-empty {@link EnumMap}.
 */
public final class ImmutableEnumMap<K extends Enum<K>, V> extends IteratorBasedImmutableMap<K, V> {
    static <K extends Enum<K>, V> ImmutableMap<K, V> asImmutable(EnumMap<K, V> map) {
        return switch (map.size()) {
            case 0 -> ImmutableMap.of();
            case 1 -> {
                Entry<K, V> entry = getOnlyElement(map.entrySet().iterator());
                yield ImmutableMap.of(entry.getKey(), entry.getValue());
            }
            default -> new ImmutableEnumMap<>(map);
        };
    }

    private final transient EnumMap<K, V> delegate;

    private ImmutableEnumMap(EnumMap<K, V> delegate) {
        this.delegate = delegate;
        if (delegate.isEmpty()) {
            throw new IllegalArgumentException();
        }
    }

    @Override
    UnmodifiableIterator<K> keyIterator() {
        return unmodifiableIterator(delegate.keySet().iterator());
    }

    @Override
    Spliterator<K> keySpliterator() {
        return delegate.keySet().spliterator();
    }

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

    @Override
    public boolean containsKey(Object key) {
        return delegate.containsKey(key);
    }

    @Override
    public V get(Object key) {
        return delegate.get(key);
    }

    @Override
    public boolean equals(Object object) {
        if (object == this) {
            return true;
        }
        if (object instanceof ImmutableEnumMap) {
            object = ((ImmutableEnumMap<?, ?>) object).delegate;
        }
        return delegate.equals(object);
    }

    @Override
    UnmodifiableIterator<Entry<K, V>> entryIterator() {
        return ImmutableEntry.unmodifiableEntryIterator(delegate.entrySet().iterator());
    }

    @Override
    Spliterator<Entry<K, V>> entrySpliterator() {
        return Spliterators.map(delegate.entrySet().spliterator(), ImmutableEntry::unmodifiableEntry);
    }

    @Override
    public void forEach(BiConsumer<? super K, ? super V> action) {
        delegate.forEach(action);
    }

    @Override
    boolean isPartialView() {
        return false;
    }

    static <T extends Object> T getOnlyElement(Iterator<T> iterator) {
        T first = iterator.next();
        if (!iterator.hasNext()) {
            return first;
        }
        StringBuilder sb = new StringBuilder().append("expected one element but was: <").append(first);
        for (int i = 0; i < 4 && iterator.hasNext(); i++) {
            sb.append(", ").append(iterator.next());
        }
        if (iterator.hasNext()) {
            sb.append(", ...");
        }
        sb.append('>');
        throw new IllegalArgumentException(sb.toString());
    }
}
