import io.appium.java_client.AppiumDriver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Pause;
import org.openqa.selenium.interactions.PointerInput;
import org.openqa.selenium.interactions.Sequence;
import org.openqa.selenium.remote.DesiredCapabilities;

import java.io.IOException;
import java.net.URL;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;

public class Edition067_Zoom_Touch_Gestures {

    private AppiumDriver driver;

    @Before
    public void setUp() throws IOException {
        DesiredCapabilities caps = new DesiredCapabilities();

        caps.setCapability("platformName", "Android");
        caps.setCapability("deviceName", "Android Emulator");
        caps.setCapability("automationName", "UiAutomator2");
        caps.setCapability("appPackage", "com.google.android.apps.maps");
        caps.setCapability("appActivity", "com.google.android.maps.MapsActivity");
        caps.setCapability("intentAction", "android.intent.action.VIEW");
        caps.setCapability("optionalIntentArguments", "-d geo:46.457398,-119.407305");


        driver = new AppiumDriver(new URL("http://localhost:4723/wd/hub"), caps);
    }

    @After
    public void tearDown() {
        try {
            driver.quit();
        } catch (Exception ign) {}
    }

    /*
        locus: the center of the touch gesture, the point that fingers are pinching away from or towards. They won't actually touch this point though
        startRadius: distance from center that fingers begin at
        endRadius: distance from center that fingers end at
        pinchAngle: at what angle the fingers pinch around the locus, in degrees. 0 for vertical pinch, 90 for horizontal pinch
        duration: the total amount of time the pinch gesture will take
    */
    private Collection<Sequence> zoom(Point locus, int startRadius, int endRadius, int pinchAngle, Duration duration) {
        // convert degree angle into radians. 0/360 is top (12 O'clock).
        double angle = Math.PI / 2 - (2 * Math.PI / 360 * pinchAngle);

        // create the gesture for one finger
        Sequence fingerAPath = zoomSinglefinger("fingerA", locus, startRadius, endRadius, angle, duration);

        // flip the angle around to the other side of the locus and get the gesture for the second finger
        angle = angle + Math.PI;
        Sequence fingerBPath = zoomSinglefinger("fingerB", locus, startRadius, endRadius, angle, duration);

        return Arrays.asList(fingerAPath, fingerBPath);
    }

    /*
        Used by the `zoom` method, for creating one half of a zooming pinch gesture.
        This will return the tough gesture for a single finger, to be put together with
        another finger action to complete the gesture.
        fingerName: name of this input finger for the gesture. Used by automation system to tell inputs apart
        locus: the center of the touch gesture, the point that fingers are pinching away from or towards. They won't actually touch this point though
        startRadius: distance from center that fingers begin at
        endRadius: distance from center that fingers end at
        angle: at what angle the fingers pinch around the locus, in radians.
        duration: the total amount of time the pinch gesture will take
     */
    private Sequence zoomSinglefinger(String fingerName, Point locus, int startRadius, int endRadius, double angle, Duration duration) {
        PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, fingerName);
        Sequence fingerPath = new Sequence(finger, 0);

        double midpointRadius = startRadius + (endRadius > startRadius ? 1 : -1) * 20;

        // find coordinates for starting point of action (converting from polar coordinates to cartesian)
        int fingerStartx = (int)Math.floor(locus.x + startRadius * Math.cos(angle));
        int fingerStarty = (int)Math.floor(locus.y - startRadius * Math.sin(angle));

        // find coordinates for first point that pingers move quickly to
        int fingerMidx = (int)Math.floor(locus.x + (midpointRadius * Math.cos(angle)));
        int fingerMidy = (int)Math.floor(locus.y - (midpointRadius * Math.sin(angle)));

        // find coordinates for ending point of action (converting from polar coordinates to cartesian)
        int fingerEndx = (int)Math.floor(locus.x + endRadius * Math.cos(angle));
        int fingerEndy = (int)Math.floor(locus.y - endRadius * Math.sin(angle));

        // move finger into start position
        fingerPath.addAction(finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), fingerStartx, fingerStarty));
        // finger comes down into contact with screen
        fingerPath.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()));
        // finger moves a small amount very quickly
        fingerPath.addAction(finger.createPointerMove(Duration.ofMillis(1), PointerInput.Origin.viewport(), fingerMidx, fingerMidy));
        // pause for a little bit
        fingerPath.addAction(new Pause(finger, Duration.ofMillis(100)));
        // finger moves to end position
        fingerPath.addAction(finger.createPointerMove(duration, PointerInput.Origin.viewport(), fingerEndx, fingerEndy));
        // finger lets up, off the screen
        fingerPath.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));

        return fingerPath;
    }

    /*
    Simplified method for zooming in.
    Defaults to a 45 degree angle for the pinch gesture.
    Defaults to a duration of 25ms
    Fingers start 200px from locus

    locus: the center of the pinch action, fingers move away from here
    distance: how far fingers move outwards, starting 100px from the locus
     */
    private Collection<Sequence> zoomIn(Point locus, int distance) {
        return zoom(locus, 200, 200 + distance, 45, Duration.ofMillis(25));
    }

    /*
    Simplified method for zooming out.
    Defaults to a 45 degree angle for the pinch gesture.
    Defaults to a duration of 25ms
    Fingers finish 200px from locus

    locus: the center of the pinch action, fingers move towards here
    distance: how far fingers move inwards, they will end 100px from the locus
     */
    private Collection<Sequence> zoomOut(Point locus, int distance) {
        return zoom(locus, 200 + distance, 200, 45, Duration.ofMillis(25));
    }

    @Test
    public void ZoomInAndOut() throws InterruptedException {
        // tap center to dismiss toolbars
        WebElement map = driver.findElementById("com.google.android.apps.maps:id/mainmap_container");
        map.click();

        Rectangle mapCoordinates = map.getRect();
        Point center = getCenter(mapCoordinates);

        driver.perform(zoomOut(center, 450));

        Thread.sleep(1000);

        driver.perform(zoomIn(center, 450));

        Thread.sleep(1000);

        driver.perform(zoomOut(center.moveBy(0, 250), 300));

        Thread.sleep(1000);

        driver.perform(zoomIn(center.moveBy(0, -250), 300));

        Thread.sleep(3000);
    }

    private Point getCenter(Rectangle rect) {
        return new Point(rect.x + rect.getWidth() / 2, rect.y + rect.getHeight() / 2);
    }
}