/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.documentsui.bots;

import android.app.UiAutomation;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiSelector;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;

/**
 * A test helper class that provides support for controlling directory list
 * and making assertions against the state of it.
 */
public class GestureBot extends Bots.BaseBot {
    private static final int LONGPRESS_STEPS = 60;
    private static final int TRAVELING_STEPS = 20;
    private static final int BAND_SELECTION_DEFAULT_STEPS = 100;
    private static final int STEPS_INBETWEEN_POINTS = 2;
    // Inserted after each motion event injection.
    private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
    private static final int LONG_PRESS_EVENT_INJECTION_DELAY_MILIS = 1000;
    private final String mDirContainerId;
    private final String mDirListId;
    private final UiAutomation mAutomation;
    private long mDownTime = 0;

    public GestureBot(UiDevice device, UiAutomation automation, Context context, int timeout) {
        super(device, context, timeout);
        mDirContainerId = mTargetPackage + ":id/container_directory";
        mDirListId = mTargetPackage + ":id/dir_list";
        mAutomation = automation;
    }

    public void gestureSelectFiles(String startLabel, String endLabel) throws Exception {
        int toolType = Configurator.getInstance().getToolType();
        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
        Rect startCoord = findDocument(startLabel).getBounds();
        Rect endCoord = findDocument(endLabel).getBounds();
        double diffX = endCoord.centerX() - startCoord.centerX();
        double diffY = endCoord.centerY() - startCoord.centerY();
        Point[] points = new Point[LONGPRESS_STEPS + TRAVELING_STEPS];

        // First simulate long-press by having a bunch of MOVE events in the same coordinate
        for (int i = 0; i < LONGPRESS_STEPS; i++) {
            points[i] = new Point(startCoord.centerX(), startCoord.centerY());
        }

        // Next put the actual drag/move events
        for (int i = 0; i < TRAVELING_STEPS; i++) {
            int newX = startCoord.centerX() + (int) (diffX / TRAVELING_STEPS * i);
            int newY = startCoord.centerY() + (int) (diffY / TRAVELING_STEPS * i);
            points[i + LONGPRESS_STEPS] = new Point(newX, newY);
        }
        mDevice.swipe(points, STEPS_INBETWEEN_POINTS);
        Configurator.getInstance().setToolType(toolType);
    }

    public void bandSelection(Point start, Point end) throws Exception {
        bandSelection(start, end, BAND_SELECTION_DEFAULT_STEPS);
    }

    public void fingerSelection(Point start, Point end) throws Exception {
        fingerSelection(start, end, BAND_SELECTION_DEFAULT_STEPS);
    }

    public void bandSelection(Point start, Point end, int steps) throws Exception {
        int toolType = Configurator.getInstance().getToolType();
        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
        swipe(start.x, start.y, end.x, end.y, steps, MotionEvent.BUTTON_PRIMARY, false);
        Configurator.getInstance().setToolType(toolType);
    }

    private void fingerSelection(Point start, Point end, int steps) throws Exception {
        int toolType = Configurator.getInstance().getToolType();
        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
        swipe(start.x, start.y, end.x, end.y, steps, MotionEvent.BUTTON_PRIMARY, true);
        Configurator.getInstance().setToolType(toolType);
    }

    public UiObject findDocument(String label) throws UiObjectNotFoundException {
        final UiSelector docList = new UiSelector().resourceId(
                mDirContainerId).childSelector(
                new UiSelector().resourceId(mDirListId));

        // Wait for the first list item to appear
        new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);

        return mDevice.findObject(docList.childSelector(new UiSelector().text(label)));
    }

    private void swipe(int downX, int downY, int upX, int upY, int steps, int button,
            boolean fingerSelection) {
        int swipeSteps = steps;
        double xStep = 0;
        double yStep = 0;

        // avoid a divide by zero
        if (swipeSteps == 0) {
            swipeSteps = 1;
        }

        xStep = ((double) (upX - downX)) / swipeSteps;
        yStep = ((double) (upY - downY)) / swipeSteps;

        // first touch starts exactly at the point requested
        touchDown(downX, downY, button);
        if (fingerSelection) {
            SystemClock.sleep(LONG_PRESS_EVENT_INJECTION_DELAY_MILIS);
        }
        for (int i = 1; i < swipeSteps; i++) {
            touchMove(downX + (int) (xStep * i), downY + (int) (yStep * i), button);
            // set some known constant delay between steps as without it this
            // become completely dependent on the speed of the system and results
            // may vary on different devices. This guarantees at minimum we have
            // a preset delay.
            SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
        }
        touchUp(upX, upY);
    }

    private boolean touchDown(int x, int y, int button) {
        long mDownTime = SystemClock.uptimeMillis();
        MotionEvent event = getMotionEvent(mDownTime, mDownTime, MotionEvent.ACTION_DOWN, button, x,
                y);
        return mAutomation.injectInputEvent(event, true);
    }

    private boolean touchUp(int x, int y) {
        final long eventTime = SystemClock.uptimeMillis();
        MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_UP, 0, x, y);
        mDownTime = 0;
        return mAutomation.injectInputEvent(event, true);
    }

    private boolean touchMove(int x, int y, int button) {
        final long eventTime = SystemClock.uptimeMillis();
        MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_MOVE, button, x,
                y);
        return mAutomation.injectInputEvent(event, true);
    }

    /** Helper function to obtain a MotionEvent. */
    private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, int button,
            float x, float y) {

        PointerProperties properties = new PointerProperties();
        properties.id = 0;
        properties.toolType = Configurator.getInstance().getToolType();

        PointerCoords coords = new PointerCoords();
        coords.pressure = 1;
        coords.size = 1;
        coords.x = x;
        coords.y = y;

        return MotionEvent.obtain(downTime, eventTime, action, 1,
                new PointerProperties[]{properties}, new PointerCoords[]{coords},
                0, button, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
    }
}
