package org.fxmisc.easybind.select;

import java.util.function.Function;

import javafx.beans.InvalidationListener;
import javafx.beans.value.ObservableValue;

class IntermediateSelectionElement<T, U, V> implements NestedSelectionElement<T, V> {
    private final InvalidationListener observableInvalidationListener = obs -> observableInvalidated();
    private final Function<? super T, ObservableValue<U>> selector;
    private final NestedSelectionElement<U, V> nested;
    private final Runnable onInvalidation;

    private ObservableValue<U> observable = null;

    public IntermediateSelectionElement(
            Runnable onInvalidation,
            Function<? super T, ObservableValue<U>> selector,
            NestedSelectionElementFactory<U, V> nestedSelectionFactory) {
        this.onInvalidation = onInvalidation;
        this.selector = selector;
        this.nested = nestedSelectionFactory.create(this::nestedInvalidated);
    }

    @Override
    public void connect(T baseVal) {
        if(isConnected()) {
            throw new IllegalStateException("Already connected");
        }

        observable = selector.apply(baseVal);
        observable.addListener(observableInvalidationListener);
    }

    @Override
    public void disconnect() {
        if(isConnected()) {
            nested.disconnect();
            observable.removeListener(observableInvalidationListener);
            observable = null;
        }
    }

    @Override
    public final boolean isConnected() {
        return observable != null;
    }

    @Override
    public V getValue() {
        if(!isConnected()) {
            throw new IllegalStateException("Not connected");
        }

        if(!nested.isConnected()) {
            U observableVal = observable.getValue();
            if(observableVal == null) {
                return null;
            }
            nested.connect(observableVal);
        }

        return nested.getValue();
    }

    private void nestedInvalidated() {
        onInvalidation.run();
    }

    private void observableInvalidated() {
        nested.disconnect();
        onInvalidation.run();
    }
}