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

package jdocs.routing;

import akka.routing.FromConfig;
import akka.routing.RoundRobinRoutingLogic;
import akka.routing.Routee;
import akka.routing.RoutingLogic;
import akka.routing.SeveralRoutees;
import akka.testkit.AkkaJUnitActorSystemResource;

import jdocs.AbstractJavaTest;
import akka.testkit.javadsl.TestKit;
import org.junit.ClassRule;
import org.junit.Test;

import static org.junit.Assert.*;

import com.typesafe.config.ConfigFactory;

import scala.collection.immutable.IndexedSeq;
import static akka.japi.Util.immutableIndexedSeq;

import docs.routing.CustomRouterDocSpec;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
// #imports1
import akka.actor.AbstractActor;

import java.util.ArrayList;
import java.util.List;

// #imports1

public class CustomRouterDocTest extends AbstractJavaTest {

  @ClassRule
  public static AkkaJUnitActorSystemResource actorSystemResource =
      new AkkaJUnitActorSystemResource(
          "CustomRouterDocTest", ConfigFactory.parseString(CustomRouterDocSpec.jconfig()));

  private final ActorSystem system = actorSystemResource.getSystem();

  public
  // #routing-logic
  static class RedundancyRoutingLogic implements RoutingLogic {
    private final int nbrCopies;

    public RedundancyRoutingLogic(int nbrCopies) {
      this.nbrCopies = nbrCopies;
    }

    RoundRobinRoutingLogic roundRobin = new RoundRobinRoutingLogic();

    @Override
    public Routee select(Object message, IndexedSeq<Routee> routees) {
      List<Routee> targets = new ArrayList<Routee>();
      for (int i = 0; i < nbrCopies; i++) {
        targets.add(roundRobin.select(message, routees));
      }
      return new SeveralRoutees(targets);
    }
  }
  // #routing-logic

  public
  // #unit-test-logic
  static final class TestRoutee implements Routee {
    public final int n;

    public TestRoutee(int n) {
      this.n = n;
    }

    @Override
    public void send(Object message, ActorRef sender) {}

    @Override
    public int hashCode() {
      return n;
    }

    @Override
    public boolean equals(Object obj) {
      return (obj instanceof TestRoutee) && n == ((TestRoutee) obj).n;
    }
  }

  // #unit-test-logic

  public static class Storage extends AbstractActor {
    @Override
    public Receive createReceive() {
      return receiveBuilder()
          .matchAny(
              message -> {
                getSender().tell(message, getSelf());
              })
          .build();
    }
  }

  @Test
  public void unitTestRoutingLogic() {
    // #unit-test-logic
    RedundancyRoutingLogic logic = new RedundancyRoutingLogic(3);

    List<Routee> routeeList = new ArrayList<Routee>();
    for (int n = 1; n <= 7; n++) {
      routeeList.add(new TestRoutee(n));
    }
    IndexedSeq<Routee> routees = immutableIndexedSeq(routeeList);

    SeveralRoutees r1 = (SeveralRoutees) logic.select("msg", routees);
    assertEquals(r1.getRoutees().get(0), routeeList.get(0));
    assertEquals(r1.getRoutees().get(1), routeeList.get(1));
    assertEquals(r1.getRoutees().get(2), routeeList.get(2));

    SeveralRoutees r2 = (SeveralRoutees) logic.select("msg", routees);
    assertEquals(r2.getRoutees().get(0), routeeList.get(3));
    assertEquals(r2.getRoutees().get(1), routeeList.get(4));
    assertEquals(r2.getRoutees().get(2), routeeList.get(5));

    SeveralRoutees r3 = (SeveralRoutees) logic.select("msg", routees);
    assertEquals(r3.getRoutees().get(0), routeeList.get(6));
    assertEquals(r3.getRoutees().get(1), routeeList.get(0));
    assertEquals(r3.getRoutees().get(2), routeeList.get(1));

    // #unit-test-logic
  }

  @Test
  public void demonstrateUsageOfCustomRouter() {
    new TestKit(system) {
      {
        // #usage-1
        for (int n = 1; n <= 10; n++) {
          system.actorOf(Props.create(Storage.class), "s" + n);
        }

        List<String> paths = new ArrayList<String>();
        for (int n = 1; n <= 10; n++) {
          paths.add("/user/s" + n);
        }

        ActorRef redundancy1 = system.actorOf(new RedundancyGroup(paths, 3).props(), "redundancy1");
        redundancy1.tell("important", getTestActor());
        // #usage-1

        for (int i = 0; i < 3; i++) {
          expectMsgEquals("important");
        }

        // #usage-2
        ActorRef redundancy2 = system.actorOf(FromConfig.getInstance().props(), "redundancy2");
        redundancy2.tell("very important", getTestActor());
        // #usage-2

        for (int i = 0; i < 5; i++) {
          expectMsgEquals("very important");
        }
      }
    };
  }
}
