package fr.ourten.teabeans.value;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiFunction;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import fr.ourten.teabeans.listener.MapValueChangeListener;

public class BaseMapProperty<K, T> extends BaseProperty<Map<K, T>> implements MapProperty<K, T>
{
    private BiFunction<T, T, T>                                   checker;

    /**
     * The list of attached listeners that need to be notified when the value
     * change.
     */
    private final ArrayList<MapValueChangeListener<K, ? super T>> mapValueChangeListeners;

    public BaseMapProperty(final Map<K, T> value, final String name)
    {
        super(value, name);
        this.mapValueChangeListeners = Lists.newArrayList();

        this.value = value != null ? Maps.newHashMap(value) : Maps.newHashMap();
    }

    public BaseMapProperty(final Map<K, T> value)
    {
        this(value, "");
    }

    @Override
    public ImmutableMap<K, T> getValue()
    {
        return ImmutableMap.copyOf(this.value);
    }

    @Override
    public void addListener(final MapValueChangeListener<K, ? super T> listener)
    {
        if (!this.mapValueChangeListeners.contains(listener))
            this.mapValueChangeListeners.add(listener);
    }

    @Override
    public void removeListener(final MapValueChangeListener<K, ? super T> listener)
    {
        this.mapValueChangeListeners.remove(listener);
    }

    private void fireListChangeListeners(final K key, final T oldValue, final T newValue)
    {
        for (final MapValueChangeListener<K, ? super T> listener : this.mapValueChangeListeners)
            listener.valueChanged(this, key, oldValue, newValue);
    }

    public BiFunction<T, T, T> getElementChecker()
    {
        return this.checker;
    }

    public void setElementChecker(final BiFunction<T, T, T> checker)
    {
        this.checker = checker;
    }

    @Override
    public T get(final K key)
    {
        return this.value.get(key);
    }

    @Override
    public T put(final K key, T value)
    {
        this.fireInvalidationListeners();
        this.fireListChangeListeners(key, null, value);

        Map<K, T> old = null;
        if (!this.valueChangeListeners.isEmpty())
            old = Maps.newHashMap(this.value);

        if (this.checker != null)
            value = this.checker.apply(null, value);

        this.value.put(key, value);
        this.fireChangeListeners(old, this.value);

        return null;
    }

    @Override
    public void putAll(Map<K, ? extends T> elements)
    {
        elements.forEach(this::put);
    }

    @Override
    public Set<Entry<K, T>> entrySet()
    {
        return this.value.entrySet();
    }

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

    @Override
    public Collection<T> values()
    {
        return this.value.values();
    }

    @Override
    public boolean containsKey(K key)
    {
        return this.value.containsKey(key);
    }

    @Override
    public boolean containsValue(T value)
    {
        return this.value.containsValue(value);
    }

    @Override
    public T remove(K key)
    {
        this.fireInvalidationListeners();
        this.fireListChangeListeners(key, this.value.get(key), null);

        Map<K, T> old = null;
        if (!this.valueChangeListeners.isEmpty())
            old = Maps.newHashMap(this.value);
        final T rtn = this.value.remove(key);
        this.fireChangeListeners(old, this.value);

        return rtn;
    }

    @Override
    public T replace(K key, T element)
    {
        this.fireInvalidationListeners();
        this.fireListChangeListeners(key, this.value.get(key), element);

        Map<K, T> old = null;
        if (!this.valueChangeListeners.isEmpty())
            old = Maps.newHashMap(this.value);

        if (this.checker != null)
            element = this.checker.apply(this.value.get(key), element);

        final T rtn = this.value.replace(key, element);
        this.fireChangeListeners(old, this.value);

        return rtn;
    }

    @Override
    public void clear()
    {
        this.setValue(Maps.newHashMap());
    }

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

}

@@ -2236,107 +2236,8 @@
   %7B%0A
-        this.fireInvalidationListeners();%0A        this.fireListChangeListeners(key, null, value);%0A%0A
     
@@ -2479,24 +2479,123 @@
 key, value);
+%0A%0A        this.fireInvalidationListeners();%0A        this.fireListChangeListeners(key, null, value);
 %0A        thi
@@ -2626,33 +2626,32 @@
 d, this.value);%0A
-%0A
         return n
@@ -2699,16 +2699,22 @@
  putAll(
+final 
 Map%3CK, ?
@@ -3121,16 +3121,22 @@
 ainsKey(
+final 
 K key)%0A 
@@ -3238,16 +3238,22 @@
 nsValue(
+final 
 T value)
@@ -3348,16 +3348,22 @@
  remove(
+final 
 K key)%0A 
@@ -3379,83 +3379,26 @@
     
-this.fireInvalidationListeners();%0A        this.fireListChangeListeners(key,
+final T oldValue =
  thi
@@ -3417,17 +3417,9 @@
 key)
-, null);%0A
+;
 %0A   
@@ -3437,32 +3437,33 @@
  T%3E old = null;%0A
+%0A
         if (!thi
@@ -3584,24 +3584,126 @@
 remove(key);
+%0A%0A        this.fireInvalidationListeners();%0A        this.fireListChangeListeners(key, oldValue, null);
 %0A        thi
@@ -3734,33 +3734,32 @@
 d, this.value);%0A
-%0A
         return r
@@ -3804,16 +3804,22 @@
 replace(
+final 
 K key, T
@@ -3846,83 +3846,26 @@
     
-this.fireInvalidationListeners();%0A        this.fireListChangeListeners(key,
+final T oldValue =
  thi
@@ -3876,36 +3876,25 @@
 lue.get(key)
-, element);%0A
+;
 %0A        Map
@@ -4172,16 +4172,121 @@
 lement);
+%0A%0A        this.fireInvalidationListeners();%0A        this.fireListChangeListeners(key, oldValue, element);
 %0A       
@@ -4321,33 +4321,32 @@
 d, this.value);%0A
-%0A
         return r
@@ -4535,7 +4535,5 @@
   %7D%0A
-%0A%7D%0A
+%7D

Fix a bug where mapproperty listeners were fired before any changes