package com.netopyr.reduxfx.patcher.property;

import com.netopyr.reduxfx.vscenegraph.property.VChangeListener;
import com.netopyr.reduxfx.vscenegraph.property.VInvalidationListener;
import com.netopyr.reduxfx.vscenegraph.property.VProperty;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ReadOnlyProperty;
import javafx.beans.value.ChangeListener;
import javafx.scene.Node;

import java.lang.invoke.MethodHandle;
import java.util.function.Consumer;

abstract class AbstractAccessor<V_TYPE, ACTION, FX_TYPE> implements Accessor<V_TYPE, ACTION> {

    private final MethodHandle propertyGetter;
    private final Consumer<ACTION> dispatcher;

    AbstractAccessor(MethodHandle propertyGetter, Consumer<ACTION> dispatcher) {
        this.propertyGetter = propertyGetter;
        this.dispatcher = dispatcher;
    }

    @Override
    public void set(Node node, VProperty<V_TYPE, ACTION> vProperty) {

        final ReadOnlyProperty<FX_TYPE> property;
        try {
            property = (ReadOnlyProperty) propertyGetter.invoke(node);
        } catch (Throwable throwable) {
            throw new IllegalStateException("Unable to read property " + vProperty.getName() + " from Node-class " + node.getClass(), throwable);
        }

        setValue(property, vToFX(vProperty.getValue()));

        if (vProperty.getChangeListener().isDefined()) {
            setChangeListener(node, property, vProperty.getChangeListener().get(), dispatcher);
        }

        if (vProperty.getInvalidationListener().isDefined()) {
            setInvalidationListener(node, property, vProperty.getInvalidationListener().get(), dispatcher);
        }
    }

    protected abstract V_TYPE fxToV(FX_TYPE value);

    protected abstract FX_TYPE vToFX(V_TYPE value);

    protected abstract void setValue(ReadOnlyProperty<FX_TYPE> property, FX_TYPE value);

    @SuppressWarnings("unchecked")
    private void setChangeListener(Node node, ReadOnlyProperty<FX_TYPE> property, VChangeListener<? super V_TYPE, ACTION> listener, Consumer<ACTION> dispatcher) {
        final ChangeListener<FX_TYPE> oldListener = (ChangeListener) node.getProperties().get(property.getName() + ".change");
        if (oldListener != null) {
            property.removeListener(oldListener);
        }

        final ChangeListener<FX_TYPE> newListener = (source, oldValue, newValue) -> {
            final ACTION action = listener.onChange(fxToV(oldValue), fxToV(newValue));
            if (action != null) {
                dispatcher.accept(action);
            }
        };
        property.addListener(newListener);
        node.getProperties().put(property.getName() + ".change", newListener);
    }

    @SuppressWarnings("unchecked")
    private void setInvalidationListener(Node node, ReadOnlyProperty<FX_TYPE> property, VInvalidationListener<ACTION> listener, Consumer<ACTION> dispatcher) {
        final InvalidationListener oldListener = (InvalidationListener) node.getProperties().get(property.getName() + ".invalidation");
        if (oldListener != null) {
            property.removeListener(oldListener);
        }

        final InvalidationListener newListener = (source) -> {
            final ACTION action = listener.onInvalidation();
            if (action != null) {
                dispatcher.accept(action);
            }
        };
        property.addListener(newListener);
        node.getProperties().put(property.getName() + ".invalidation", newListener);
    }
}

@@ -827,24 +827,59 @@
 her;%0A    %7D%0A%0A
+    @SuppressWarnings(%22unchecked%22)%0A
     @Overrid
@@ -1275,32 +1275,73 @@
 le);%0A        %7D%0A%0A
+        clearListeners(node, property);%0A%0A
         setValue
@@ -1982,33 +1982,29 @@
 te void 
-setChange
+clear
 Listener
 (Node no
@@ -1987,32 +1987,33 @@
 id clearListener
+s
 (Node node, Read
@@ -2046,150 +2046,315 @@
 erty
-, VChangeListener%3C? super V_TYPE, ACTION%3E listener, Consumer%3CACTION%3E dispatcher) %7B%0A        final ChangeListener%3CFX_TYPE%3E oldListener = (Change
+) %7B%0A        final ChangeListener%3CFX_TYPE%3E changeListener = (ChangeListener) node.getProperties().get(property.getName() + %22.change%22);%0A        if (changeListener != null) %7B%0A            property.removeListener(changeListener);%0A        %7D%0A%0A        final InvalidationListener invalidationListener = (Invalidation
 List
@@ -2399,38 +2399,44 @@
 y.getName() + %22.
-change
+invalidation
 %22);%0A        if (
@@ -2427,35 +2427,44 @@
 %22);%0A        if (
-old
+invalidation
 Listener != null
@@ -2495,35 +2495,44 @@
 .removeListener(
-old
+invalidation
 Listener);%0A     
@@ -2528,32 +2528,201 @@
 ner);%0A        %7D%0A
+    %7D%0A%0A    private void setChangeListener(Node node, ReadOnlyProperty%3CFX_TYPE%3E property, VChangeListener%3C? super V_TYPE, ACTION%3E listener, Consumer%3CACTION%3E dispatcher) %7B
 %0A        final C
@@ -3114,43 +3114,8 @@
  %7D%0A%0A
-    @SuppressWarnings(%22unchecked%22)%0A
     
@@ -3273,240 +3273,8 @@
 ) %7B%0A
-        final InvalidationListener oldListener = (InvalidationListener) node.getProperties().get(property.getName() + %22.invalidation%22);%0A        if (oldListener != null) %7B%0A            property.removeListener(oldListener);%0A        %7D%0A%0A
     

Fix issue with listeners that fired when they should not