package com.alangpierce.lambdacalculusplayground.expressioncontroller;

import android.widget.RelativeLayout;

import com.alangpierce.lambdacalculusplayground.ScreenExpression;
import com.alangpierce.lambdacalculusplayground.TopLevelExpressionManager;
import com.alangpierce.lambdacalculusplayground.drag.PointerMotionEvent;
import com.alangpierce.lambdacalculusplayground.dragdrop.DragSource;
import com.alangpierce.lambdacalculusplayground.geometry.Point;
import com.alangpierce.lambdacalculusplayground.geometry.Views;
import com.alangpierce.lambdacalculusplayground.userexpression.UserExpression;
import com.alangpierce.lambdacalculusplayground.userexpression.UserExpressions;
import com.alangpierce.lambdacalculusplayground.userexpression.UserFuncCall;
import com.alangpierce.lambdacalculusplayground.userexpression.UserLambda;
import com.alangpierce.lambdacalculusplayground.userexpression.UserVariable;
import com.alangpierce.lambdacalculusplayground.view.TopLevelExpressionView;
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 TopLevelExpressionControllerImpl implements TopLevelExpressionController {
    private final TopLevelExpressionManager topLevelExpressionManager;
    private final TopLevelExpressionView view;
    private final RelativeLayout rootView;

    private ScreenExpression screenExpression;
    private ExpressionController expressionController;
    private OnTopLevelChangeCallback onChangeCallback;

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

    public TopLevelExpressionControllerImpl(
            TopLevelExpressionManager topLevelExpressionManager, TopLevelExpressionView view,
            RelativeLayout rootView, ScreenExpression screenExpression,
            ExpressionController expressionController) {
        this.topLevelExpressionManager = topLevelExpressionManager;
        this.view = view;
        this.rootView = rootView;
        this.screenExpression = screenExpression;
        this.expressionController = expressionController;
    }

    @Override
    public ScreenExpression getScreenExpression() {
        return screenExpression;
    }

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

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

    @Override
    public void handlePositionChange(Point screenPos) {
        Point canvasPos = screenPos.minus(Views.getScreenPos(rootView));
        screenExpression = ScreenExpression.create(screenExpression.getExpr(), canvasPos);
        onChangeCallback.onChange(this);
    }

    public void handleExprChange(ExpressionController newExpressionController) {
        UserExpression newExpression = newExpressionController.getExpression();
        screenExpression = ScreenExpression.create(newExpression, screenExpression.getCanvasPos());
        boolean isExecutable = UserExpressions.canStep(newExpression);
        view.handleExpressionChange(
                newExpressionController.getView(), screenExpression.getCanvasPos(), isExecutable);
        newExpressionController.setOnChangeCallback(this::handleExprChange);
        expressionController = newExpressionController;
        updateDragActionSubscription();
        onChangeCallback.onChange(this);
    }

    private void updateDragActionSubscription() {
        if (dragActionSubscription != null) {
            dragActionSubscription.unsubscribe();
            dragActionSubscription = null;
        }
        dragActionSubscription = view.getExpressionObservable().subscribe(dragActionSubject);
    }

    private void handleExecuteClick() {
        @Nullable UserExpression newExpr = UserExpressions.evaluate(screenExpression.getExpr());
        if (newExpr == null) {
            // For now, just ignore expressions that infinite loop.
            return;
        }
        topLevelExpressionManager.createNewExpression(
                newExpr, view.getScreenPos().plus(Point.create(100, 150)));
    }

    @Override
    public List<DragSource> getDragSources() {
        // TODO: Put this in a better place or re-purpose this function.
        view.setOnExecuteListener(this::handleExecuteClick);

        updateDragActionSubscription();
        return ImmutableList.of(new TopLevelExpressionDragSource());
    }

    @Override
    public ExpressionController decommission() {
        onChangeCallback.onChange(null);
        if (dragActionSubscription != null) {
            dragActionSubscription.unsubscribe();
        }
        view.decommission();
        return expressionController;
    }

    @Override
    public boolean isTrivialExpression() {
        return screenExpression.getExpr().visit(new UserExpression.UserExpressionVisitor<Boolean>() {
            @Override
            public Boolean visit(UserLambda lambda) {
                return lambda.body == null;
            }
            @Override
            public Boolean visit(UserFuncCall funcCall) {
                return false;
            }
            @Override
            public Boolean visit(UserVariable variable) {
                return true;
            }
        });
    }

    private class TopLevelExpressionDragSource implements DragSource {
        @Override
        public Observable<? extends Observable<PointerMotionEvent>> getDragObservable() {
            return dragActionSubject;
        }
        @Override
        public TopLevelExpressionController handleStartDrag() {
            return TopLevelExpressionControllerImpl.this;
        }
    }
}

@@ -65,16 +65,80 @@
 oller;%0A%0A
+import android.view.View;%0Aimport android.view.View.MeasureSpec;%0A
 import a
@@ -4277,16 +4277,61 @@
 %0A       
+ TopLevelExpressionController newExpression =
  topLeve
@@ -4442,13 +4442,837 @@
 00, 
-150))
+200)));%0A%0A        Point newScreenPos = computeExecuteResultScreenPos(newExpression);%0A        newExpression.getView().setScreenPos(newScreenPos);%0A        newExpression.handlePositionChange(newScreenPos);%0A    %7D%0A%0A    private Point computeExecuteResultScreenPos(TopLevelExpressionController newExpression) %7B%0A        Point thisViewPos = view.getScreenPos();%0A        int thisViewWidth = view.getNativeView().getWidth();%0A        int thisViewHeight = view.getNativeView().getHeight();%0A%0A        newExpression.getView().getNativeView().measure(%0A                MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);%0A        int viewWidth = newExpression.getView().getNativeView().getMeasuredWidth();%0A        return Point.create(thisViewPos.getX() + (thisViewWidth / 2) - (viewWidth / 2),%0A                thisViewPos.getY() + thisViewHeight + 50
 );%0A 

Change computed expressions to be centered below the bottom of the original expression