package com.alangpierce.lambdacalculusplayground.expressioncontroller;

import com.alangpierce.lambdacalculusplayground.TopLevelExpressionManager;
import com.alangpierce.lambdacalculusplayground.drag.PointerMotionEvent;
import com.alangpierce.lambdacalculusplayground.dragdrop.DragSource;
import com.alangpierce.lambdacalculusplayground.dragdrop.DropTarget;
import com.alangpierce.lambdacalculusplayground.expressioncontroller.FuncCallDropTarget.FuncCallControllerFactory;
import com.alangpierce.lambdacalculusplayground.geometry.Point;
import com.alangpierce.lambdacalculusplayground.userexpression.UserExpression;
import com.alangpierce.lambdacalculusplayground.userexpression.UserFuncCall;
import com.alangpierce.lambdacalculusplayground.view.ExpressionView;
import com.alangpierce.lambdacalculusplayground.view.FuncCallView;
import com.google.common.collect.ImmutableList;

import java.util.List;

import javax.annotation.Nullable;

import rx.Observable;
import rx.Subscription;
import rx.subjects.PublishSubject;
import rx.subjects.Subject;

public class FuncCallExpressionController implements ExpressionController {
    private final TopLevelExpressionManager topLevelExpressionManager;
    private final FuncCallView view;

    /*
     * State kept by this class. Since this class corresponds to an actual Android view, we need to
     * care about what it's logically a part of as it moves around.
     */
    private ExpressionController funcController;
    private ExpressionController argController;
    private UserFuncCall userFuncCall;
    private OnChangeCallback onChangeCallback;

    private final Subject<Observable<PointerMotionEvent>,Observable<PointerMotionEvent>>
            argDragActionSubject = PublishSubject.create();
    private @Nullable Subscription argDragActionSubscription;

    public FuncCallExpressionController(
            TopLevelExpressionManager topLevelExpressionManager, FuncCallView view,
            ExpressionController funcController, ExpressionController argController,
            UserFuncCall userFuncCall) {
        this.topLevelExpressionManager = topLevelExpressionManager;
        this.view = view;
        this.funcController = funcController;
        this.argController = argController;
        this.userFuncCall = userFuncCall;
    }

    @Override
    public UserExpression getExpression() {
        return userFuncCall;
    }

    @Override
    public ExpressionView getView() {
        return view;
    }

    @Override
    public void setOnChangeCallback(OnChangeCallback onChangeCallback) {
        this.onChangeCallback = onChangeCallback;
    }

    @Override
    public OnChangeCallback getOnChangeCallback() {
        return onChangeCallback;
    }

    @Override
    public List<DragSource> getDragSources() {
        updateDragActionSubscription();
        return ImmutableList.of(new ArgDragSource());
    }

    @Override
    public List<DropTarget> getDropTargets(FuncCallControllerFactory funcCallFactory) {
        return ImmutableList.of(new FuncCallDropTarget(this, view, funcCallFactory));
    }

    public void handleFuncChange(ExpressionController newFuncController) {
        userFuncCall = new UserFuncCall(newFuncController.getExpression(), userFuncCall.arg);
        view.handleFuncChange(newFuncController.getView());
        newFuncController.setOnChangeCallback(this::handleFuncChange);
        funcController = newFuncController;
        onChangeCallback.onChange(this);
    }

    public void handleArgChange(ExpressionController newArgController) {
        userFuncCall = new UserFuncCall(userFuncCall.func, newArgController.getExpression());
        view.handleArgChange(newArgController.getView());
        updateDragActionSubscription();
        newArgController.setOnChangeCallback(this::handleArgChange);
        argController = newArgController;
        onChangeCallback.onChange(this);
    }

    private void updateDragActionSubscription() {
        if (argDragActionSubscription != null) {
            argDragActionSubscription.unsubscribe();
            argDragActionSubscription = null;
        }
        argDragActionSubscription = view.getArgObservable().subscribe(argDragActionSubject);
    }

    private void decommission() {
        if (argDragActionSubscription != null) {
            argDragActionSubscription.unsubscribe();
        }
        view.decommission();
    }

    private class ArgDragSource implements DragSource {
        @Override
        public Observable<? extends Observable<PointerMotionEvent>> getDragObservable() {
            return argDragActionSubject;
        }
        @Override
        public TopLevelExpressionController handleStartDrag() {
            Point screenPos = view.getScreenPos();
            decommission();
            onChangeCallback.onChange(funcController);
            return topLevelExpressionManager.sendExpressionToTopLevel(argController, screenPos);
        }
    }
}

@@ -2273,38 +2273,73 @@
 = userFuncCall;%0A
+        setFuncViewEnabled(false);%0A
     %7D%0A
-
 %0A    @Override%0A 
@@ -3171,32 +3171,193 @@
 ncController) %7B%0A
+        setFuncViewEnabled(true);%0A        // Start accepting touch events for the arg view.%0A        funcController.getView().getNativeView().setEnabled(false);%0A%0A
         userFunc
@@ -3642,32 +3642,67 @@
 onChange(this);%0A
+        setFuncViewEnabled(false);%0A
     %7D%0A%0A    publi
@@ -4450,32 +4450,66 @@
 ecommission() %7B%0A
+        setFuncViewEnabled(true);%0A
         if (argD
@@ -4596,32 +4596,32 @@
 be();%0A        %7D%0A
-
         view.dec
@@ -4632,32 +4632,687 @@
 ssion();%0A    %7D%0A%0A
+    /**%0A     * Enable or disable touch events for the function view. The behavior of function calls is that%0A     * the left side should not itself be draggable, and should fall through to the function call%0A     * drag handler. Unfortunately, the drag observable code always consumes motion events on any%0A     * views that have ever been registered, so it's hard to make it fall through in that case.%0A     * Instead, we tell Android to skip the touch handler completely by setting its enabled state to%0A     * false.%0A     */%0A    private void setFuncViewEnabled(boolean enabled) %7B%0A        funcController.getView().getNativeView().setEnabled(enabled);%0A    %7D%0A%0A
     private clas

Fix bug where func views would steal click events