/**
 * @flow
 */
import {emptyPath, step} from './ExprPaths';
import {getPositionOnScreen} from './ViewTracker';
import {ptInRect, ptMinusPt} from './Geometry';

import * as t from './types';
import type {
    DragData,
    DropResult,
    ExprPath,
    PointDifference,
    ScreenPoint,
    State,
    UserExpression,
} from './types';

type TouchResult = {
    exprId: number,
    offset: PointDifference,
}

/**
 * Given a click on the screen, figure out what expression it is under, if any.
 */
export const resolveTouch = (state: State, point: ScreenPoint): ?TouchResult => {
    let result = null;
    for (let [exprId] of state.screenExpressions) {
        const viewKey = t.newExpressionKey(emptyPath(exprId));
        const screenRect = getPositionOnScreen(viewKey);
        if (!screenRect) {
            return;
        }
        // TODO: Deal with tiebreaking.
        if (ptInRect(point, screenRect)) {
            result = {
                exprId,
                offset: ptMinusPt(point, screenRect.topLeft),
            };
        }
    }
    return result;
};

/**
 * Given a dragged item, determine the action that would happen if the item was
 * dropped. This is useful both to perform the drop and to do highlighting.
 */
export const resolveDrop = (
        state: State, dragData: DragData, touchPos: ScreenPoint):
        DropResult => {
    for (let [path, expr] of yieldAllExpressions(state)) {
        if (expr.type === 'userLambda' && !expr.body) {
            const rect = getPositionOnScreen(t.newEmptyBodyKey(path));
            if (rect && ptInRect(touchPos, rect)) {
                return t.newInsertAsBodyResult(path, dragData.screenExpr.expr);
            }
        }
    }
    return t.newAddToTopLevelResult(dragData.screenExpr);
};

const yieldAllExpressions = function* (state: State):
        Generator<[ExprPath, UserExpression], void, void> {
    for (let [exprId, screenExpr] of state.screenExpressions) {
        yield* yieldExpressions(screenExpr.expr, emptyPath(exprId))
    }
};

const yieldExpressions = function* (expr: UserExpression, path: ExprPath):
        Generator<[ExprPath, UserExpression], void, void> {
    yield [path, expr];
    yield* t.matchUserExpression(expr, {
        userLambda: function* ({body}) {
            if (body) {
                yield* yieldExpressions(body, step(path, 'body'));
            }
        },
        userFuncCall: function* ({func, arg}) {
            yield* yieldExpressions(func, step(path, 'func'));
            yield* yieldExpressions(arg, step(path, 'arg'));
        },
        userVariable: function* () {},
        userReference: function* () {},
    })
};
@@ -312,16 +312,29 @@
 ession,%0A
+    ViewKey,%0A
 %7D from '
@@ -1364,24 +1364,235 @@
 Result =%3E %7B%0A
+    const intersectsWithView = (key: ViewKey): bool =%3E %7B%0A        const rect = getPositionOnScreen(key);%0A        return !!rect && ptInRect(touchPos, rect);%0A    %7D;%0A%0A    const yieldLambdaDrops = function* () %7B%0A    
     for (let
@@ -1642,24 +1642,28 @@
 ) %7B%0A        
+    
 if (expr.typ
@@ -1664,17 +1664,17 @@
 pr.type 
-=
+!
 == 'user
@@ -1685,12 +1685,11 @@
 da' 
-&& !
+%7C%7C 
 expr
@@ -1713,40 +1713,70 @@
     
-const rect = getPositionOnScreen
+    continue;%0A            %7D%0A            if (intersectsWithView
 (t.n
@@ -1796,17 +1796,19 @@
 y(path))
-;
+) %7B
 %0A       
@@ -1816,47 +1816,19 @@
     
-if (rect && ptInRect(touchPos, rect)) %7B
+    yield %5B
 %0A   
@@ -1836,30 +1836,27 @@
             
-return
+   
  t.newInsert
@@ -1899,16 +1899,155 @@
 pr.expr)
+,%0A                    // The lambda body should show up as above the lambda.%0A                    path.pathSteps.size + 1,%0A                %5D
 ;%0A      
@@ -2073,65 +2073,526 @@
    %7D
+;%0A
 %0A    
-return t.newAddToTopLevelResult(dragData.screenExpr)
+// Yields the drop result and priority.%0A    const yieldDropCandidates = function* ():%0A        Generator%3C%5BDropResult, number%5D, void, void%3E %7B%0A        yield* yieldLambdaDrops(state, touchPos);%0A    %7D;%0A%0A    let bestPriority = 0;%0A    let bestResult = t.newAddToTopLevelResult(dragData.screenExpr);%0A    for (let %5BdropResult, priority%5D of yieldDropCandidates(state)) %7B%0A        if (priority %3E bestPriority) %7B%0A            bestResult = dropResult;%0A            bestPriority = priority;%0A        %7D%0A    %7D%0A    return bestResult
 ;%0A%7D;

Refactor lambda hit testing to use priorities