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

package jdocs.routing;

import akka.testkit.AkkaJUnitActorSystemResource;

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

import akka.actor.ActorSystem;

//#imports1
import akka.actor.AbstractActor;
import akka.routing.ConsistentHashingRouter.ConsistentHashable;

import java.util.Map;
import java.util.HashMap;
import java.io.Serializable;
//#imports1


//#imports2
import akka.actor.Props;
import akka.actor.ActorRef;
import akka.routing.ConsistentHashingPool;
import akka.routing.ConsistentHashingRouter.ConsistentHashMapper;
import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope;
//#imports2

public class ConsistentHashingRouterDocTest extends AbstractJavaTest {

  @ClassRule
  public static AkkaJUnitActorSystemResource actorSystemResource =
    new AkkaJUnitActorSystemResource("ConsistentHashingRouterDocTest");

  private final ActorSystem system = actorSystemResource.getSystem();

  static
  //#cache-actor
  public class Cache extends AbstractActor {
    Map<String, String> cache = new HashMap<String, String>();

    @Override
    public Receive createReceive() {
      return receiveBuilder()
        .match(Entry.class, entry -> {
          cache.put(entry.key, entry.value);
        })
        .match(Get.class, get -> {
          Object value = cache.get(get.key);
          getSender().tell(value == null ? NOT_FOUND : value, getSelf());
        })
        .match(Evict.class, evict -> {
          cache.remove(evict.key);
        })
        .build();
    }
  }

  //#cache-actor
  static
  //#cache-actor
  public final class Evict implements Serializable {
    private static final long serialVersionUID = 1L;
    public final String key;
    public Evict(String key) {
      this.key = key;
    }
  }

  //#cache-actor
  static
  //#cache-actor
  public final class Get implements Serializable, ConsistentHashable {
    private static final long serialVersionUID = 1L;
    public final String key;
    public Get(String key) {
      this.key = key;
    }
    public Object consistentHashKey() {
      return key;
    } 
  }

  //#cache-actor
  static
  //#cache-actor
  public final class Entry implements Serializable {
    private static final long serialVersionUID = 1L;
    public final String key;
    public final String value;
    public Entry(String key, String value) {
      this.key = key;
      this.value = value;
    }
  }

  //#cache-actor
  static
  //#cache-actor
  public final String NOT_FOUND = "NOT_FOUND";
  //#cache-actor  


  @Test
  public void demonstrateUsageOfConsistentHashableRouter() {

    new TestKit(system) {{

      //#consistent-hashing-router      
      
      final ConsistentHashMapper hashMapper = new ConsistentHashMapper() {
        @Override
        public Object hashKey(Object message) {
          if (message instanceof Evict) {
            return ((Evict) message).key;
          } else {
            return null;
          }
        }
      };

      ActorRef cache = system.actorOf(
          new ConsistentHashingPool(10).withHashMapper(hashMapper).props(
              Props.create(Cache.class)), 
        "cache");

      cache.tell(new ConsistentHashableEnvelope(
        new Entry("hello", "HELLO"), "hello"), getRef());
      cache.tell(new ConsistentHashableEnvelope(
        new Entry("hi", "HI"), "hi"), getRef());

      cache.tell(new Get("hello"), getRef());
      expectMsgEquals("HELLO");

      cache.tell(new Get("hi"), getRef());
      expectMsgEquals("HI");

      cache.tell(new Evict("hi"), getRef());
      cache.tell(new Get("hi"), getRef());
      expectMsgEquals(NOT_FOUND);

      //#consistent-hashing-router
    }};
  }

}
