/*
 * Copyright (c) 2016, Aegir Labs LLC. All rights reserved.
 */

package com.aegir.test;

import android.app.Activity;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;

import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;

import com.aegir.demo.MapActivity;
import com.aegir.vmms.android.ui.MapView;
import com.aegir.vmms.vmd.model.MapUnit;
import com.aegir.vmms.wayfinding.model.Waypoint;

import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
 * Smoke test for the post-parity demo. Launches MapActivity directly with the
 * canonical bundled venue (AEGIRH), drives the wayfinding flow through the
 * activity's public surface, and asserts that turn-by-turn directions arrive.
 *
 * Predates the Compose conversion - this version no longer pokes the legacy
 * btn_directions_action / lv_directions XML widgets and relies on the same
 * MapActivity public API the Compose overlay invokes.
 */
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RunWith(AndroidJUnit4.class)
public class WayfindingUITest {

    private static final long MAP_LOAD_TIMEOUT_MS = 30_000;
    private static final long DIRECTIONS_TIMEOUT_MS = 15_000;

    @Test
    public void findsDirectionsBetweenTwoUnits() throws InterruptedException {
        Intent intent = new Intent(InstrumentationRegistry.getInstrumentation().getTargetContext(), MapActivity.class);
        intent.putExtra("venue_id", "AEGIRH");

        try (ActivityScenario<MapActivity> scenario = ActivityScenario.launch(intent)) {
            AtomicReference<MapActivity> activityRef = new AtomicReference<>();
            scenario.onActivity(activityRef::set);

            MapActivity activity = activityRef.get();
            assertNotNull("MapActivity was not created", activity);

            waitForMapLoaded(activity);

            MapView mapView = activity.getMapView();
            assertNotNull("MapView not created", mapView);
            assertNotNull("Map data not loaded", mapView.getMap());

            MapUnit start = null;
            MapUnit end = null;
            for (MapUnit unit : mapView.getMap()
                    .getBuildingWithId("building_1")
                    .getFloorWithId("floor_b1_1")
                    .getMapUnits()) {
                if (start == null && "Birch Meeting Room A".equals(unit.getName())) start = unit;
                if (end == null && "Grand Ballroom".equals(unit.getName())) end = unit;
                if (start != null && end != null) break;
            }
            assertNotNull("Start unit not found", start);
            assertNotNull("End unit not found", end);

            MapUnit s = start;
            MapUnit e = end;
            new Handler(Looper.getMainLooper()).post(() -> {
                activity.getWalkingPathOverlay().setSelectingPoints(true);
                activity.didSelectUnit(s);
                activity.didSelectUnit(e);
            });

            waitForBoth(activity);

            new Handler(Looper.getMainLooper()).post(() -> {
                Waypoint sw = activity.getWalkingPathOverlay().getStartingWaypoint();
                Waypoint ew = activity.getWalkingPathOverlay().getEndingWaypoint();
                if (sw != null && ew != null) {
                    com.aegir.vmms.wayfinding.model.WayfindingOptions opts =
                            new com.aegir.vmms.wayfinding.model.WayfindingOptions(true, false);
                    mapView.getMap().findWaypathBetweenWaypoints(sw, ew, opts, activity,
                            new com.aegir.vmms.android.model.AndroidCallback(new Handler(Looper.getMainLooper())));
                }
            });

            waitForDirections(activity);
            assertTrue("No directions produced",
                    activity.getWalkingPathOverlay().getDirections().size() > 0);
        }
    }

    private static void waitForMapLoaded(MapActivity activity) throws InterruptedException {
        long deadline = System.currentTimeMillis() + MAP_LOAD_TIMEOUT_MS;
        while (System.currentTimeMillis() < deadline) {
            AtomicBoolean ready = new AtomicBoolean(false);
            CountDownLatch latch = new CountDownLatch(1);
            new Handler(Looper.getMainLooper()).post(() -> {
                MapView mv = activity.getMapView();
                ready.set(mv != null && mv.getMap() != null);
                latch.countDown();
            });
            latch.await(2, TimeUnit.SECONDS);
            if (ready.get()) return;
            Thread.sleep(250);
        }
        throw new AssertionError("MapView did not load within " + MAP_LOAD_TIMEOUT_MS + "ms");
    }

    private static void waitForBoth(MapActivity activity) throws InterruptedException {
        long deadline = System.currentTimeMillis() + 5_000;
        while (System.currentTimeMillis() < deadline) {
            AtomicBoolean ready = new AtomicBoolean(false);
            CountDownLatch latch = new CountDownLatch(1);
            new Handler(Looper.getMainLooper()).post(() -> {
                ready.set(activity.getWalkingPathOverlay().getStartingWaypoint() != null
                        && activity.getWalkingPathOverlay().getEndingWaypoint() != null);
                latch.countDown();
            });
            latch.await(2, TimeUnit.SECONDS);
            if (ready.get()) return;
            Thread.sleep(100);
        }
    }

    private static void waitForDirections(MapActivity activity) throws InterruptedException {
        long deadline = System.currentTimeMillis() + DIRECTIONS_TIMEOUT_MS;
        while (System.currentTimeMillis() < deadline) {
            AtomicBoolean ready = new AtomicBoolean(false);
            CountDownLatch latch = new CountDownLatch(1);
            new Handler(Looper.getMainLooper()).post(() -> {
                ready.set(activity.getWalkingPathOverlay().getDirections() != null
                        && activity.getWalkingPathOverlay().getDirections().size() > 0);
                latch.countDown();
            });
            latch.await(2, TimeUnit.SECONDS);
            if (ready.get()) return;
            Thread.sleep(250);
        }
    }
}
