/*
 * Copyright (c) 2008-2014 MongoDB, Inc.
 * Copyright (c) 2008-2014 Atlassian Pty Ltd
 *
 * 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.bson.util;

import com.mongodb.annotations.ThreadSafe;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static com.mongodb.assertions.Assertions.notNull;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Collections.unmodifiableSet;

/**
 * Abstract base class for COW {@link java.util.Map} implementations that delegate to an internal map.
 *
 * @param <K> The key type
 * @param <V> The value type
 * @param <M> the internal {@link java.util.Map} or extension for things like sorted and navigable maps.
 */
@ThreadSafe
abstract class AbstractCopyOnWriteMap<K, V, M extends Map<K, V>> implements ConcurrentMap<K, V> {

    // @GuardedBy("lock")
    private volatile M delegate;

    // import edu.umd.cs.findbugs.annotations.@SuppressWarnings
    private final transient Lock lock = new ReentrantLock();

    // private final transient EntrySet entrySet = new EntrySet();
    // private final transient KeySet keySet = new KeySet();
    // private final transient Values values = new Values();
    // private final View.Type viewType;
    private final View<K, V> view;

    /**
     * Create a new {@link CopyOnWriteMap} with the supplied {@link Map} to initialize the values.
     *
     * @param map      the initial map to initialize with
     * @param viewType for writable or read-only key, value and entrySet views
     */
    protected <N extends Map<? extends K, ? extends V>> AbstractCopyOnWriteMap(final N map, final View.Type viewType) {
        this.delegate = notNull("delegate", copy(notNull("map", map)));
        this.view = notNull("viewType", viewType).get(this);
    }

    /**
     * Copy function, implemented by sub-classes.
     *
     * @param <N> the map to copy and return.
     * @param map the initial values of the newly created map.
     * @return a new map. Will never be modified after construction.
     */
    // @GuardedBy("lock")
    abstract <N extends Map<? extends K, ? extends V>> M copy(N map);

    //
    // mutable operations
    //

    public final void clear() {
        lock.lock();
        try {
            set(copy(Collections.<K, V>emptyMap()));
        } finally {
            lock.unlock();
        }
    }

    public final V remove(final Object key) {
        lock.lock();
        try {
            // short circuit if key doesn't exist
            if (!delegate.containsKey(key)) {
                return null;
            }
            M map = copy();
            try {
                return map.remove(key);
            } finally {
                set(map);
            }
        } finally {
            lock.unlock();
        }
    }

    public boolean remove(final Object key, final Object value) {
        lock.lock();
        try {
            if (delegate.containsKey(key) && equals(value, delegate.get(key))) {
                M map = copy();
                map.remove(key);
                set(map);
                return true;
            } else {
                return false;
            }
        } finally {
            lock.unlock();
        }
    }

    public boolean replace(final K key, final V oldValue, final V newValue) {
        lock.lock();
        try {
            if (!delegate.containsKey(key) || !equals(oldValue, delegate.get(key))) {
                return false;
            }
            M map = copy();
            map.put(key, newValue);
            set(map);
            return true;
        } finally {
            lock.unlock();
        }
    }

    public V replace(final K key, final V value) {
        lock.lock();
        try {
            if (!delegate.containsKey(key)) {
                return null;
            }
            M map = copy();
            try {
                return map.put(key, value);
            } finally {
                set(map);
            }
        } finally {
            lock.unlock();
        }
    }

    public final V put(final K key, final V value) {
        lock.lock();
        try {
            M map = copy();
            try {
                return map.put(key, value);
            } finally {
                set(map);
            }
        } finally {
            lock.unlock();
        }
    }

    public V putIfAbsent(final K key, final V value) {
        lock.lock();
        try {
            if (!delegate.containsKey(key)) {
                M map = copy();
                try {
                    return map.put(key, value);
                } finally {
                    set(map);
                }
            }
            return delegate.get(key);
        } finally {
            lock.unlock();
        }
    }

    public final void putAll(final Map<? extends K, ? extends V> t) {
        lock.lock();
        try {
            M map = copy();
            map.putAll(t);
            set(map);
        } finally {
            lock.unlock();
        }
    }

    protected M copy() {
        lock.lock();
        try {
            return copy(delegate);
        } finally {
            lock.unlock();
        }
    }

    // @GuardedBy("lock")
    protected void set(final M map) {
        delegate = map;
    }

    //
    // Collection views
    //

    public final Set<Entry<K, V>> entrySet() {
        return view.entrySet();
    }

    public final Set<K> keySet() {
        return view.keySet();
    }

    public final Collection<V> values() {
        return view.values();
    }

    //
    // delegate operations
    //

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

    public final boolean containsValue(final Object value) {
        return delegate.containsValue(value);
    }

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

    public final boolean isEmpty() {
        return delegate.isEmpty();
    }

    public final int size() {
        return delegate.size();
    }

    @Override
    public final boolean equals(final Object o) {
        return delegate.equals(o);
    }

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

    protected final M getDelegate() {
        return delegate;
    }

    @Override
    public String toString() {
        return delegate.toString();
    }

    //
    // inner classes
    //

    private class KeySet extends CollectionView<K> implements Set<K> {

        @Override
        Collection<K> getDelegate() {
            return delegate.keySet();
        }

        //
        // mutable operations
        //

        public void clear() {
            lock.lock();
            try {
                M map = copy();
                map.keySet().clear();
                set(map);
            } finally {
                lock.unlock();
            }
        }

        public boolean remove(final Object o) {
            return AbstractCopyOnWriteMap.this.remove(o) != null;
        }

        public boolean removeAll(final Collection<?> c) {
            lock.lock();
            try {
                M map = copy();
                try {
                    return map.keySet().removeAll(c);
                } finally {
                    set(map);
                }
            } finally {
                lock.unlock();
            }
        }

        public boolean retainAll(final Collection<?> c) {
            lock.lock();
            try {
                M map = copy();
                try {
                    return map.keySet().retainAll(c);
                } finally {
                    set(map);
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private final class Values extends CollectionView<V> {

        @Override
        Collection<V> getDelegate() {
            return delegate.values();
        }

        public void clear() {
            lock.lock();
            try {
                M map = copy();
                map.values().clear();
                set(map);
            } finally {
                lock.unlock();
            }
        }

        public boolean remove(final Object o) {
            lock.lock();
            try {
                if (!contains(o)) {
                    return false;
                }
                M map = copy();
                try {
                    return map.values().remove(o);
                } finally {
                    set(map);
                }
            } finally {
                lock.unlock();
            }
        }

        public boolean removeAll(final Collection<?> c) {
            lock.lock();
            try {
                M map = copy();
                try {
                    return map.values().removeAll(c);
                } finally {
                    set(map);
                }
            } finally {
                lock.unlock();
            }
        }

        public boolean retainAll(final Collection<?> c) {
            lock.lock();
            try {
                M map = copy();
                try {
                    return map.values().retainAll(c);
                } finally {
                    set(map);
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private class EntrySet extends CollectionView<Entry<K, V>> implements Set<Map.Entry<K, V>> {

        @Override
        Collection<java.util.Map.Entry<K, V>> getDelegate() {
            return delegate.entrySet();
        }

        public void clear() {
            lock.lock();
            try {
                M map = copy();
                map.entrySet().clear();
                set(map);
            } finally {
                lock.unlock();
            }
        }

        public boolean remove(final Object o) {
            lock.lock();
            try {
                if (!contains(o)) {
                    return false;
                }
                M map = copy();
                try {
                    return map.entrySet().remove(o);
                } finally {
                    set(map);
                }
            } finally {
                lock.unlock();
            }
        }

        public boolean removeAll(final Collection<?> c) {
            lock.lock();
            try {
                M map = copy();
                try {
                    return map.entrySet().removeAll(c);
                } finally {
                    set(map);
                }
            } finally {
                lock.unlock();
            }
        }

        public boolean retainAll(final Collection<?> c) {
            lock.lock();
            try {
                M map = copy();
                try {
                    return map.entrySet().retainAll(c);
                } finally {
                    set(map);
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private static class UnmodifiableIterator<T> implements Iterator<T> {
        private final Iterator<T> delegate;

        UnmodifiableIterator(final Iterator<T> delegate) {
            this.delegate = delegate;
        }

        public boolean hasNext() {
            return delegate.hasNext();
        }

        public T next() {
            return delegate.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    protected abstract static class CollectionView<E> implements Collection<E> {

        abstract Collection<E> getDelegate();

        //
        // delegate operations
        //

        public final boolean contains(final Object o) {
            return getDelegate().contains(o);
        }

        public final boolean containsAll(final Collection<?> c) {
            return getDelegate().containsAll(c);
        }

        public final Iterator<E> iterator() {
            return new UnmodifiableIterator<E>(getDelegate().iterator());
        }

        public final boolean isEmpty() {
            return getDelegate().isEmpty();
        }

        public final int size() {
            return getDelegate().size();
        }

        public final Object[] toArray() {
            return getDelegate().toArray();
        }

        public final <T> T[] toArray(final T[] a) {
            return getDelegate().toArray(a);
        }

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

        @Override
        public boolean equals(final Object obj) {
            return getDelegate().equals(obj);
        }

        @Override
        public String toString() {
            return getDelegate().toString();
        }

        //
        // unsupported operations
        //

        public final boolean add(final E o) {
            throw new UnsupportedOperationException();
        }

        public final boolean addAll(final Collection<? extends E> c) {
            throw new UnsupportedOperationException();
        }
    }

    private boolean equals(final Object o1, final Object o2) {
        if (o1 == null) {
            return o2 == null;
        }
        return o1.equals(o2);
    }

    /**
     * Provides access to the views of the underlying key, value and entry collections.
     */
    public abstract static class View<K, V> {
        View() {
        }

        abstract Set<K> keySet();

        abstract Set<Entry<K, V>> entrySet();

        abstract Collection<V> values();

        /**
         * The different types of {@link View} available
         */
        public enum Type {
            STABLE {
                @Override
                <K, V, M extends Map<K, V>> View<K, V> get(final AbstractCopyOnWriteMap<K, V, M> host) {
                    return host.new Immutable();
                }
            },
            LIVE {
                @Override
                <K, V, M extends Map<K, V>> View<K, V> get(final AbstractCopyOnWriteMap<K, V, M> host) {
                    return host.new Mutable();
                }
            };

            abstract <K, V, M extends Map<K, V>> View<K, V> get(AbstractCopyOnWriteMap<K, V, M> host);
        }
    }

    final class Immutable extends View<K, V> {

        @Override
        public Set<K> keySet() {
            return unmodifiableSet(delegate.keySet());
        }

        @Override
        public Set<Entry<K, V>> entrySet() {
            return unmodifiableSet(delegate.entrySet());
        }

        @Override
        public Collection<V> values() {
            return unmodifiableCollection(delegate.values());
        }
    }

    final class Mutable extends View<K, V> {

        private final transient KeySet keySet = new KeySet();
        private final transient EntrySet entrySet = new EntrySet();
        private final transient Values values = new Values();

        @Override
        public Set<K> keySet() {
            return keySet;
        }

        @Override
        public Set<Entry<K, V>> entrySet() {
            return entrySet;
        }

        @Override
        public Collection<V> values() {
            return values;
        }
    }
}

