/*
 * Copyright (C) 2009-2021 Lightbend Inc. <https://www.lightbend.com>
 */

package akka.actor;

import akka.event.Logging;
import akka.event.Logging.LoggerInitialized;
import akka.japi.Creator;
import akka.japi.Pair;
import akka.japi.Util;
import akka.japi.tuple.Tuple22;
import akka.japi.tuple.Tuple4;
import akka.routing.GetRoutees;
import akka.routing.FromConfig;
import akka.routing.NoRouter;
import akka.testkit.AkkaJUnitActorSystemResource;
import akka.testkit.AkkaSpec;
import akka.testkit.TestProbe;

import org.junit.ClassRule;
import org.junit.Test;
import org.scalatestplus.junit.JUnitSuite;
import scala.Option;

import java.util.Optional;

import static org.junit.Assert.*;

public class JavaAPI extends JUnitSuite {

  @ClassRule
  public static AkkaJUnitActorSystemResource actorSystemResource =
      new AkkaJUnitActorSystemResource("JavaAPI", AkkaSpec.testConf());

  private final ActorSystem system = actorSystemResource.getSystem();

  // compilation tests
  @SuppressWarnings("unused")
  public void mustCompile() {
    final Kill kill = Kill.getInstance();
    final PoisonPill pill = PoisonPill.getInstance();
    final ReceiveTimeout t = ReceiveTimeout.getInstance();

    final LocalScope ls = LocalScope.getInstance();
    final NoScopeGiven noscope = NoScopeGiven.getInstance();

    final LoggerInitialized x = Logging.loggerInitialized();

    final GetRoutees r = GetRoutees.getInstance();
    final NoRouter nr = NoRouter.getInstance();
    final FromConfig fc = FromConfig.getInstance();

    final ActorPath p = ActorPaths.fromString("akka://Sys@localhost:1234/user/abc");
  }

  @Test
  public void mustBeAbleToCreateActorRefFromClass() {
    ActorRef ref = system.actorOf(Props.create(JavaAPITestActor.class));
    assertNotNull(ref);
  }

  @SuppressWarnings("unchecked")
  public static Props mkErasedProps() {
    return Props.create(
        JavaAPITestActor.class,
        new Creator() {
          public Object create() {
            return new JavaAPITestActor();
          }
        });
  }

  public static Props mkPropsWithLambda() {
    return Props.create(JavaAPITestActor.class, JavaAPITestActor::new);
  }

  @Test
  public void mustBeAbleToCreateActorRefFromFactory() {
    ActorRef ref = system.actorOf(mkPropsWithLambda());
    assertNotNull(ref);
  }

  @Test
  public void mustBeAbleToCreateActorRefFromErasedFactory() {
    ActorRef ref = system.actorOf(mkErasedProps());
    assertNotNull(ref);
  }

  @Test
  public void mustBeAbleToCreateActorWIthConstructorParams() {
    ActorRef ref =
        system.actorOf(
            Props.create(ActorWithConstructorParams.class, "a", "b", Integer.valueOf(17), 18));
    final TestProbe probe = new TestProbe(system);
    probe.send(ref, "get");
    probe.expectMsg("a-b-17-18");
  }

  @Test
  public void mustBeAbleToCreateActorWIthBoxedAndUnBoxedConstructorParams() {
    ActorRef ref =
        system.actorOf(
            Props.create(ActorWithConstructorParams.class, "a", "b", 17, Integer.valueOf(18)));
    final TestProbe probe = new TestProbe(system);
    probe.send(ref, "get");
    probe.expectMsg("a-b-17-18");
  }

  @Test
  public void mustBeAbleToCreateActorWIthNullConstructorParams() {
    ActorRef ref =
        system.actorOf(Props.create(ActorWithConstructorParams.class, "a", null, null, 18));
    final TestProbe probe = new TestProbe(system);
    probe.send(ref, "get");
    probe.expectMsg("a-null-null-18");
  }

  @Test
  public void mustBeAbleToCreateActorWIthNullConstructorParams2() {
    // without this Object array wrapper it will not compile: "reference to create is ambiguous"
    ActorRef ref =
        system.actorOf(Props.create(ActorWithConstructorParams.class, new Object[] {null}));
    final TestProbe probe = new TestProbe(system);
    probe.send(ref, "get");
    probe.expectMsg("null-undefined-0-0");
  }

  @Test
  public void mustBeAbleToCreateActorWIthNullConstructorParams3() {
    ActorRef ref = system.actorOf(Props.create(ActorWithConstructorParams.class, "a", null));
    final TestProbe probe = new TestProbe(system);
    probe.send(ref, "get");
    probe.expectMsg("a-null-0-0");
  }

  @Test
  @SuppressWarnings("unused")
  public void mustCompileTupleCreation() {
    final Pair<Integer, String> p = Pair.create(1, "2");
    final Tuple4<Integer, String, Integer, Long> t4 = Tuple4.create(1, "2", 3, 4L);
    Tuple22.create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22);
  }

  @Test
  public void mustBeAbleToCreateOptionFromOptional() {
    Option<Object> empty = Util.option(Optional.ofNullable(null));
    assertTrue(empty.isEmpty());

    Option<String> full = Util.option(Optional.ofNullable("hello"));
    assertTrue(full.isDefined());
  }

  public static class ActorWithConstructorParams extends UntypedAbstractActor {

    private final String a;
    private final String b;
    private final Integer c;
    private final int d;

    public ActorWithConstructorParams(String a, String b, Integer c, int d) {
      this.a = a;
      this.b = b;
      this.c = c;
      this.d = d;
    }

    public ActorWithConstructorParams(String a, Object b) {
      this.a = a;
      this.b = String.valueOf(b);
      this.c = 0;
      this.d = 0;
    }

    public ActorWithConstructorParams(String a, int d) {
      this.a = a;
      this.b = "undefined";
      this.c = 0;
      this.d = d;
    }

    public ActorWithConstructorParams(Object a) {
      this.a = String.valueOf(a);
      this.b = "undefined";
      this.c = 0;
      this.d = 0;
    }

    public ActorWithConstructorParams(int d) {
      this.a = "undefined";
      this.b = "undefined";
      this.c = 0;
      this.d = d;
    }

    public void onReceive(Object msg) {
      String reply =
          String.valueOf(a)
              + "-"
              + String.valueOf(b)
              + "-"
              + String.valueOf(c)
              + "-"
              + String.valueOf(d);
      getSender().tell(reply, getSelf());
    }
  }
}
