// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package dev.flutter.scenarios;

import static org.junit.Assert.fail;

import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.internal.runner.junit4.statement.UiThreadStatement;
import androidx.test.runner.AndroidJUnit4;
import com.google.common.util.concurrent.SettableFuture;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.dart.DartExecutor;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public class EngineLaunchE2ETest {
  @Test
  public void smokeTestEngineLaunch() throws Throwable {
    Context applicationContext = InstrumentationRegistry.getTargetContext();
    // Specifically, create the engine without running FlutterMain first.
    final AtomicReference<FlutterEngine> engine = new AtomicReference<>();

    // Run the production under test on the UI thread instead of annotating the whole test
    // as @UiThreadTest because having the message handler and the CompletableFuture both being
    // on the same thread will create deadlocks.
    UiThreadStatement.runOnUiThread(() -> engine.set(new FlutterEngine(applicationContext)));
    SettableFuture<Boolean> statusReceived = SettableFuture.create();

    // Resolve locale to `en_US`.
    // This is required, so `window.locale` in populated in dart.
    // TODO: Fix race condition between sending this over the channel and starting the entrypoint.
    // https://github.com/flutter/flutter/issues/55999
    UiThreadStatement.runOnUiThread(
        () -> engine.get().getLocalizationChannel().sendLocales(Arrays.asList(Locale.US)));

    // The default Dart main entrypoint sends back a platform message on the "waiting_for_status"
    // channel. That will be our launch success assertion condition.
    engine
        .get()
        .getDartExecutor()
        .setMessageHandler(
            "waiting_for_status", (byteBuffer, binaryReply) -> statusReceived.set(Boolean.TRUE));

    // Launching the entrypoint will run the Dart code that sends the "waiting_for_status" platform
    // message.
    UiThreadStatement.runOnUiThread(
        () ->
            engine
                .get()
                .getDartExecutor()
                .executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault()));

    try {
      Boolean result = statusReceived.get(10, TimeUnit.SECONDS);
      if (!result) {
        fail("expected message on waiting_for_status not received");
      }
    } catch (ExecutionException e) {
      fail(e.getMessage());
    } catch (InterruptedException e) {
      fail(e.getMessage());
    } catch (TimeoutException e) {
      fail("timed out waiting for engine started signal");
    }
    // If it gets to here, statusReceived is true.
  }
}
