/*
 * (C) Copyright 2016 Pantheon Technologies, s.r.o. and others.
 *
 * Licensed 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.opendaylight.yangtools.triemap;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.opendaylight.yangtools.triemap.LookupResult.RESTART;

import com.google.common.annotations.Beta;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;

/**
 * This is a port of Scala's TrieMap class from the Scala Collections library. This implementation does not support
 * null keys nor null values.
 *
 * @author Aleksandar Prokopec (original Scala implementation)
 * @author Roman Levenstein (original Java 6 port)
 * @author Robert Varga
 *
 * @param <K> the type of keys maintained by this map
 * @param <V> the type of mapped values
 */
@Beta
public abstract class TrieMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K,V>, Serializable {
    private static final long serialVersionUID = 1L;

    private final Equivalence<? super K> equiv;

    private AbstractEntrySet<K, V> entrySet;
    private AbstractKeySet<K> keySet;

    TrieMap(final Equivalence<? super K> equiv) {
        this.equiv = equiv;
    }

    public static <K, V> TrieMap<K, V> create() {
        return new MutableTrieMap<>(Equivalence.equals());
    }

    /**
     * Returns a snapshot of this TrieMap. This operation is lock-free and
     * linearizable.
     *
     * <p>
     * The snapshot is lazily updated - the first time some branch in the
     * snapshot or this TrieMap are accessed, they are rewritten. This means
     * that the work of rebuilding both the snapshot and this TrieMap is
     * distributed across all the threads doing updates or accesses subsequent
     * to the snapshot creation.
     */
    public abstract TrieMap<K, V> mutableSnapshot();

    /**
     * Returns a read-only snapshot of this TrieMap. This operation is lock-free
     * and linearizable.
     *
     * <p>
     * The snapshot is lazily updated - the first time some branch of this
     * TrieMap are accessed, it is rewritten. The work of creating the snapshot
     * is thus distributed across subsequent updates and accesses on this
     * TrieMap by all threads. Note that the snapshot itself is never rewritten
     * unlike when calling the `snapshot` method, but the obtained snapshot
     * cannot be modified.
     *
     * <p>
     * This method is used by other methods such as `size` and `iterator`.
     */
    public abstract ImmutableTrieMap<K, V> immutableSnapshot();

    @Override
    public final boolean containsKey(final Object key) {
        return get(key) != null;
    }

    @Override
    public final boolean containsValue(final Object value) {
        return super.containsValue(checkNotNull(value));
    }

    @Override
    public final Set<Entry<K, V>> entrySet() {
        AbstractEntrySet<K, V> ret = entrySet;
        if (ret == null) {
            entrySet = ret = createEntrySet();
        }
        return ret;
    }

    @Override
    public final Set<K> keySet() {
        AbstractKeySet<K> ret = keySet;
        if (ret == null) {
            keySet = ret = createKeySet();
        }
        return ret;
    }

    @Override
    public final V get(final Object key) {
        @SuppressWarnings("unchecked")
        final K k = (K) checkNotNull(key);
        return lookuphc(k, computeHash(k));
    }

    @Override
    public abstract void clear();

    @Override
    public abstract V put(K key, V value);

    @Override
    public abstract V putIfAbsent(K key, V value);

    @Override
    public abstract V remove(Object key);

    @Override
    public abstract boolean remove(Object key, Object value);

    @Override
    public abstract boolean replace(K key, V oldValue, V newValue);

    @Override
    public abstract V replace(K key, V value);

    @Override
    public abstract int size();

    /* internal methods implemented by subclasses */

    abstract AbstractEntrySet<K, V> createEntrySet();

    abstract AbstractKeySet<K> createKeySet();

    abstract boolean isReadOnly();

    abstract INode<K, V> RDCSS_READ_ROOT(boolean abort);

    /**
     * Return an iterator over a TrieMap.
     *
     * <p>
     * If this is a read-only snapshot, it would return a read-only iterator.
     *
     * <p>
     * If it is the original TrieMap or a non-readonly snapshot, it would return
     * an iterator that would allow for updates.
     *
     * @return An iterator.
     */
    abstract AbstractIterator<K, V> iterator();

    /* internal methods provided for subclasses */

    /**
     * Return an iterator over a TrieMap. This is a read-only iterator.
     *
     * @return A read-only iterator.
     */
    final ImmutableIterator<K, V> immutableIterator() {
        return new ImmutableIterator<>(immutableSnapshot());
    }

    @SuppressWarnings("null")
    static <V> V toNullable(final Optional<V> opt) {
        return opt.orElse(null);
    }

    final int computeHash(final K key) {
        return equiv.hash(key);
    }

    final Object writeReplace() throws ObjectStreamException {
        return new SerializationProxy(immutableSnapshot(), isReadOnly());
    }

    /* package-protected utility methods */

    final Equivalence<? super K> equiv() {
        return equiv;
    }

    final INode<K, V> readRoot() {
        return RDCSS_READ_ROOT(false);
    }

    // FIXME: abort = false by default
    final INode<K, V> readRoot(final boolean abort) {
        return RDCSS_READ_ROOT(abort);
    }

    final INode<K, V> RDCSS_READ_ROOT() {
        return RDCSS_READ_ROOT(false);
    }

    final boolean equal(final K k1, final K k2) {
        return equiv.equivalent(k1, k2);
    }

    /* private implementation methods */

    @SuppressWarnings("unchecked")
    private V lookuphc(final K key, final int hc) {
        Object res;
        do {
            // Keep looping as long as RESTART is being indicated
            res = RDCSS_READ_ROOT().rec_lookup(key, hc, 0, null, this);
        } while (res == RESTART);

        return (V) res;
    }
}

@@ -3472,32 +3472,38 @@
 Set() %7B%0A        
+final 
 AbstractEntrySet
@@ -3516,19 +3516,8 @@
  ret
- = entrySet
 ;%0A  
@@ -3526,39 +3526,49 @@
     
-if
+return
  (ret =
-= null) %7B%0A            
+ entrySet) != null ? ret : (
 entr
@@ -3569,30 +3569,24 @@
  (entrySet =
- ret =
  createEntry
@@ -3594,38 +3594,9 @@
 et()
-;%0A        %7D%0A        return ret
+)
 ;%0A  
@@ -3657,16 +3657,22 @@
         
+final 
 Abstract
@@ -3688,17 +3688,8 @@
  ret
- = keySet
 ;%0A  
@@ -3698,47 +3698,55 @@
     
-if
+return
  (ret =
-= null) %7B%0A            
+ keySet) != null ? ret : (
 keySet =
  ret
@@ -3745,14 +3745,8 @@
 et =
- ret =
  cre
@@ -3760,38 +3760,9 @@
 et()
-;%0A        %7D%0A        return ret
+)
 ;%0A  

improve keySet()/entrySet() bytecode