package com.alangpierce.lambdacalculusplayground.view;

import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

import com.alangpierce.lambdacalculusplayground.drag.DragObservableGenerator;
import com.alangpierce.lambdacalculusplayground.drag.PointerMotionEvent;
import com.alangpierce.lambdacalculusplayground.geometry.CanvasPoint;
import com.alangpierce.lambdacalculusplayground.geometry.DrawableAreaPoint;
import com.alangpierce.lambdacalculusplayground.geometry.PointConverter;
import com.alangpierce.lambdacalculusplayground.geometry.PointDifference;
import com.alangpierce.lambdacalculusplayground.geometry.ScreenPoint;
import com.alangpierce.lambdacalculusplayground.geometry.Views;

import autovalue.shaded.com.google.common.common.base.Preconditions;
import rx.Observable;

public class TopLevelExpressionView {
    private final DragObservableGenerator dragObservableGenerator;
    private final PointConverter pointConverter;
    private final RelativeLayout rootView;

    private ExpressionView exprView;
    // The execute button, which may or may not be attached to the root view.
    private View executeButton;
    private boolean isExecutable;

    public TopLevelExpressionView(DragObservableGenerator dragObservableGenerator,
            PointConverter pointConverter, RelativeLayout rootView, ExpressionView exprView,
            View executeButton,  boolean isExecutable) {
        this.dragObservableGenerator = dragObservableGenerator;
        this.pointConverter = pointConverter;
        this.rootView = rootView;
        this.exprView = exprView;
        this.executeButton = executeButton;
        this.isExecutable = isExecutable;
    }

    public static TopLevelExpressionView render(
            ExpressionViewRenderer renderer, DragObservableGenerator dragObservableGenerator,
            PointConverter pointConverter, RelativeLayout rootView, ExpressionView exprView,
            boolean isExecutable) {
        View executeButton = renderer.makeExecuteButton();
        return new TopLevelExpressionView(dragObservableGenerator, pointConverter, rootView,
                exprView, executeButton, isExecutable);
    }

    public ScreenPoint getScreenPos() {
        return Views.getScreenPos(exprView.getNativeView());
    }

    public void attachToRoot(CanvasPoint canvasPos) {
        Preconditions.checkState(exprView.getNativeView().getParent() == null,
                "Cannot attach an expression to the root that is already attached.");
        LinearLayout exprNativeView = exprView.getNativeView();
        DrawableAreaPoint drawableAreaPoint = pointConverter.toDrawableAreaPoint(canvasPos);
        rootView.addView(exprNativeView, Views.layoutParamsForRelativePos(drawableAreaPoint));
        invalidateExecuteButton(drawableAreaPoint);
    }

    public void setScreenPos(ScreenPoint screenPos) {
        exprView.getNativeView().setLayoutParams(
                Views.layoutParamsForRelativePos(pointConverter.toDrawableAreaPoint(screenPos)));

        // TODO: Consolidate with invalidateExecuteButton.
        LinearLayout exprNativeView = exprView.getNativeView();
        ScreenPoint executeScreenPos = screenPos.plus(PointDifference.create(
                exprNativeView.getMeasuredWidth() - 40,
                exprNativeView.getMeasuredHeight() - 40));
        Views.updateLayoutParamsToRelativePos(executeButton,
                pointConverter.toDrawableAreaPoint(executeScreenPos));
    }

    public void setCanvasPos(DrawableAreaPoint canvasPos) {
        exprView.getNativeView().setLayoutParams(Views.layoutParamsForRelativePos(canvasPos));
        invalidateExecuteButton(canvasPos);
    }

    public LinearLayout getNativeView() {
        return exprView.getNativeView();
    }

    public Observable<? extends Observable<PointerMotionEvent>> getExpressionObservable() {
        return dragObservableGenerator.getDragObservable(exprView.getNativeView());
    }

    public void startDrag() {
        exprView.getNativeView().animate()
                .setDuration(100).translationZBy(10).scaleX(1.05f).scaleY(1.05f);
        executeButton.animate().setDuration(150).alpha(0);
        executeButton.setClickable(false);
    }

    public void endDrag() {
        exprView.getNativeView().animate()
                .setDuration(100).translationZBy(-10).scaleX(1.0f).scaleY(1.0f);
        executeButton.animate().setDuration(150).alpha(1);
        executeButton.setClickable(true);
    }

    public void handleExpressionChange(
            ExpressionView newExpression, DrawableAreaPoint drawableAreaPoint,
            boolean newIsExecutable) {
        rootView.removeView(exprView.getNativeView());
        rootView.addView(newExpression.getNativeView());
        exprView = newExpression;
        isExecutable = newIsExecutable;
        setCanvasPos(drawableAreaPoint);
    }

    public void decommission() {
        rootView.removeView(exprView.getNativeView());
        rootView.removeView(executeButton);
    }

    public interface OnExecuteListener {
        void execute();
    }

    public void setOnExecuteListener(OnExecuteListener listener) {
        executeButton.setOnClickListener((view) -> listener.execute());
    }

    private void invalidateExecuteButton(DrawableAreaPoint drawableAreaPoint) {
        rootView.removeView(executeButton);
        if (isExecutable) {
            LinearLayout exprNativeView = exprView.getNativeView();
            exprNativeView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
            Views.updateLayoutParamsToRelativePos(executeButton, drawableAreaPoint.plus(
                    PointDifference.create(exprNativeView.getMeasuredWidth() - 40,
                            exprNativeView.getMeasuredHeight() - 40)));
            rootView.addView(executeButton);
        }
    }
}

@@ -3045,446 +3045,142 @@
     
-// TODO: Consolidate with invalidateExecuteButton.%0A        LinearLayout exprNativeView = exprView.getNativeView();%0A        ScreenPoint executeScreenPos = screenPos.plus(PointDifference.create(%0A                exprNativeView.getMeasuredWidth() - 40,%0A                exprNativeView.getMeasuredHeight() - 40));%0A        Views.updateLayoutParamsToRelativePos(executeButton,%0A                pointConverter.toDrawableAreaPoint(executeScree
+DrawableAreaPoint expressionPos = pointConverter.toDrawableAreaPoint(screenPos);%0A        recomputeExecuteButtonPosition(expressio
 nPos)
-)
 ;%0A  
@@ -4995,33 +4995,29 @@
 eaPoint 
-drawableAreaPoint
+expressionPos
 ) %7B%0A    
@@ -5084,24 +5084,224 @@
 able) %7B%0A    
+        recomputeExecuteButtonPosition(expressionPos);%0A            rootView.addView(executeButton);%0A        %7D%0A    %7D%0A%0A    private void recomputeExecuteButtonPosition(DrawableAreaPoint expressionPos) %7B%0A
         Line
@@ -5352,36 +5352,32 @@
 View();%0A        
- 
-   
 exprNativeView.m
@@ -5456,109 +5456,149 @@
     
-    Views.updateLayoutParamsToRelativePos(executeButton, drawableAreaPoint.plus(%0A                    
+executeButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);%0A        DrawableAreaPoint executePos = expressionPos.plus(
 Poin
@@ -5612,24 +5612,41 @@
 ence.create(
+%0A                
 exprNativeVi
@@ -5673,24 +5673,48 @@
 ) - 
-40,%0A            
+(executeButton.getMeasuredWidth() / 4),%0A
     
@@ -5766,69 +5766,124 @@
 ) - 
-40)));%0A            rootView.addView(executeButton);%0A        %7D
+(executeButton.getMeasuredHeight() / 4)));%0A        Views.updateLayoutParamsToRelativePos(executeButton, executePos);
 %0A   

Consolidate execute button positioning, make it no longer rely on pixels