/*******n***********************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *******************************************************************************/
package org.apache.ofbiz.base.util.collections;

import java.io.Serializable;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

import org.apache.ofbiz.base.lang.Appender;
import org.apache.ofbiz.base.util.UtilGenerics;

@SuppressWarnings("serial")
public abstract class GenericMap<K, V> implements Appender<StringBuilder>, Map<K, V>, Serializable {
    private static final AtomicReferenceFieldUpdater<GenericMap<?, ?>, Set<?>> KEY_SET_UPDATER =
            UtilGenerics.cast(AtomicReferenceFieldUpdater.newUpdater(GenericMap.class, Set.class, "keySet"));
    private static final AtomicReferenceFieldUpdater<GenericMap<?, ?>, Set<?>> ENTRY_SET_UPDATER =
            UtilGenerics.cast(AtomicReferenceFieldUpdater.newUpdater(GenericMap.class, Set.class, "entrySet"));
    private static final AtomicReferenceFieldUpdater<GenericMap<?, ?>, Collection<?>> VALUES_UPDATER =
            UtilGenerics.cast(AtomicReferenceFieldUpdater.newUpdater(GenericMap.class, Collection.class, "values"));
    private static final AtomicIntegerFieldUpdater<GenericMap<?, ?>> MOD_COUNT_UPDATER =
            UtilGenerics.cast(AtomicIntegerFieldUpdater.newUpdater(GenericMap.class, "modCount"));

    private volatile Set<K> keySet;
    private volatile Set<Map.Entry<K, V>> entrySet;
    private volatile Collection<V> values;
    private volatile int modCount;

    /**
     * Gets mod count.
     * @return the mod count
     */
    public int getModCount() {
        return modCount;
    }

    /**
     * Increment mod count.
     */
    protected void incrementModCount() {
        MOD_COUNT_UPDATER.getAndIncrement(this);
    }

    @Override
    public final void clear() {
        if (isEmpty()) {
            return;
        }
        incrementModCount();
        clearInternal();
    }

    protected abstract void clearInternal();

    @Override
    public boolean containsValue(Object value) {
        return values().contains(value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(entrySet, keySet, values);
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Map<?, ?>)) {
            return false;
        }
        if (this == o) {
            return true;
        }
        Map<?, ?> map = (Map<?, ?>) o;
        if (size() != map.size()) {
            return false;
        }
        if (o instanceof GenericMap<?, ?>) {
            return equalsGenericMap((GenericMap<?, ?>) o);
        }
        return equalsMap(map);
    }

    /**
     * Equals generic map boolean.
     * @param map the map
     * @return the boolean
     */
    private boolean equalsGenericMap(GenericMap<?, ?> map) {
        Iterator<Map.Entry<K, V>> it = iterator(false);
        while (it.hasNext()) {
            Map.Entry<K, V> entry = it.next();
            K key = entry.getKey();
            V value = entry.getValue();
            if (value != null) {
                if (!value.equals(map.get(key, false))) {
                    return false;
                }
            } else {
                Object otherValue = map.get(key, false);
                if (otherValue != null) {
                    return false;
                }
                if (!map.containsKey(key)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Equals map boolean.
     * @param map the map
     * @return the boolean
     */
    private boolean equalsMap(Map<?, ?> map) {
        Iterator<Map.Entry<K, V>> it = iterator(false);
        while (it.hasNext()) {
            Map.Entry<K, V> entry = it.next();
            K key = entry.getKey();
            V value = entry.getValue();
            if (value != null) {
                if (!value.equals(map.get(key))) {
                    return false;
                }
            } else {
                Object otherValue = map.get(key);
                if (otherValue != null) {
                    return false;
                }
                if (!map.containsKey(key)) {
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public final V get(Object key) {
        return get(key, true);
    }

    protected abstract V get(Object key, boolean noteAccess);

    protected abstract class GenericMapIterator<DEST> extends IteratorWrapper<DEST, Map.Entry<K, V>> {
        private final int currentModCount = getModCount();

        protected GenericMapIterator(boolean noteAccess) {
            super(iterator(noteAccess));
        }

        /**
         * Is valid boolean.
         * @param src the src
         * @return the boolean
         */
        protected boolean isValid(Map.Entry<K, V> src) {
            if (currentModCount != getModCount()) {
                throw new ConcurrentModificationException();
            }
            return true;
        }
    }

    @Override
    public final Set<Map.Entry<K, V>> entrySet() {
        if (entrySet == null) {
            ENTRY_SET_UPDATER.compareAndSet(this, null, new GenericMapEntrySet<K, V, GenericMap<K, V>>(this) {
                @Override
                protected boolean contains(Object key, Object value) {
                    return Objects.equals(get(key, false), value);
                }

                @Override
                public Iterator<Map.Entry<K, V>> iterator(boolean noteAccess) {
                    return new GenericMapIterator<Map.Entry<K, V>>(noteAccess) {
                        @Override
                        protected void noteRemoval(Map.Entry<K, V> dest, Map.Entry<K, V> src) {
                            // No need to note the remove, the wrapped iterator does that for us
                        }

                        @Override
                        protected Map.Entry<K, V> convert(Map.Entry<K, V> src) {
                            return src;
                        }
                    };
                }
            });
        }
        return entrySet;
    }

    protected abstract Iterator<Map.Entry<K, V>> iterator(boolean noteAccess);

    @Override
    public final Set<K> keySet() {
        if (keySet == null) {
            KEY_SET_UPDATER.compareAndSet(this, null, new GenericMapKeySet<K, V, GenericMap<K, V>>(this) {
                @Override
                public boolean contains(Object key) {
                    return containsKey(key);
                }

                @Override
                public Iterator<K> iterator(boolean noteAccess) {
                    return new GenericMapIterator<K>(noteAccess) {
                        @Override
                        protected void noteRemoval(K dest, Map.Entry<K, V> src) {
                            // No need to note the remove, the wrapped iterator does that for us
                        }

                        @Override
                        protected K convert(Map.Entry<K, V> src) {
                            return src.getKey();
                        }
                    };
                }
            });
        }
        return keySet;
    }

    @Override
    public final Collection<V> values() {
        if (values == null) {
            VALUES_UPDATER.compareAndSet(this, null, new GenericMapValues<K, V, GenericMap<K, V>>(this) {
                @Override
                public Iterator<V> iterator(boolean noteAccess) {
                    return new GenericMapIterator<V>(noteAccess) {
                        @Override
                        protected void noteRemoval(V dest, Map.Entry<K, V> src) {
                            // No need to note the remove, the wrapped iterator does that for us
                        }

                        @Override
                        protected V convert(Map.Entry<K, V> src) {
                            return src.getValue();
                        }
                    };
                }
            });
        }
        return values;
    }

    @Override
    public final V remove(Object key) {
        return removeInternal(key, true);
    }

    protected abstract V removeInternal(Object key, boolean incrementModCount);

    @Override
    public final void putAll(Map<? extends K, ? extends V> map) {
        putAllInternal(map);
    }

    private <KE extends K, VE extends V> void putAllInternal(Map<KE, VE> map) {
        if (map.isEmpty()) {
            return;
        }
        incrementModCount();
        Iterator<Map.Entry<KE, VE>> it;
        if (map instanceof GenericMap<?, ?>) {
            GenericMap<KE, VE> otherMap = UtilGenerics.cast(map);
            it = otherMap.iterator(false);
        } else {
            it = map.entrySet().iterator();
        }
        putAllIterator(it);
    }

    protected abstract <KE extends K, VE extends V> void putAllIterator(Iterator<Map.Entry<KE, VE>> it);

    @Override
    public String toString() {
        return appendTo(new StringBuilder()).toString();
    }

    @Override
    public StringBuilder appendTo(StringBuilder sb) {
        sb.append("{");
        Iterator<Map.Entry<K, V>> it = iterator(false);
        while (it.hasNext()) {
            Map.Entry<K, V> entry = it.next();
            sb.append(entry.getKey()).append("=").append(entry.getValue());
            if (it.hasNext()) {
                sb.append(",");
            }
        }
        return sb.append("}");
    }
}

