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

package jdocs.actor.fsm;

import akka.actor.*;
import jdocs.AbstractJavaTest;
import akka.testkit.javadsl.TestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import static org.junit.Assert.*;

import static jdocs.actor.fsm.FSMDocTest.StateType.*;
import static jdocs.actor.fsm.FSMDocTest.Messages.*;

import java.time.Duration;

public class FSMDocTest extends AbstractJavaTest {
  static ActorSystem system;

  @BeforeClass
  public static void setup() {
    system = ActorSystem.create("FSMDocTest");
  }

  @AfterClass
  public static void tearDown() {
    TestKit.shutdownActorSystem(system);
    system = null;
  }

  public static enum StateType {
    SomeState,
    Processing,
    Idle,
    Active,
    Error
  }

  public static enum Messages {
    WillDo,
    Tick
  }

  public static enum Data {
    Foo,
    Bar
  };

  public static interface X {};

  public static class DummyFSM extends AbstractFSM<StateType, Integer> {
    Integer newData = 42;
    //#alt-transition-syntax
    public void handler(StateType from, StateType to) {
      // handle transition here
    }

    //#alt-transition-syntax
    {
    //#modifier-syntax
    when(SomeState, matchAnyEvent((msg, data) -> {
        return goTo(Processing).using(newData).
          forMax(Duration.ofSeconds(5)).replying(WillDo);
    }));
    //#modifier-syntax

    //#NullFunction
    when(SomeState, AbstractFSM.NullFunction());
    //#NullFunction

    //#transition-syntax
    onTransition(
      matchState(Idle, Active, () -> setTimer("timeout",
        Tick, Duration.ofSeconds(1L), true)).
      state(Active, null, () -> cancelTimer("timeout")).
      state(null, Idle, (f, t) -> log().info("entering Idle from " + f)));
    //#transition-syntax

    //#alt-transition-syntax
    onTransition(this::handler);
    //#alt-transition-syntax

    //#stop-syntax
    when(Error, matchEventEquals("stop", (event, data) -> {
      // do cleanup ...
      return stop();
    }));
    //#stop-syntax

    //#termination-syntax
    onTermination(
      matchStop(Normal(),
        (state, data) -> {/* Do something here */}).
        stop(Shutdown(),
          (state, data) -> {/* Do something here */}).
        stop(Failure.class,
          (reason, state, data) -> {/* Do something here */}));
    //#termination-syntax

    //#unhandled-syntax
    whenUnhandled(
      matchEvent(X.class, (x, data) -> {
        log().info("Received unhandled event: " + x);
        return stay();
      }).
      anyEvent((event, data) -> {
        log().warning("Received unknown event: " + event);
        return goTo(Error);
      }));
    }
    //#unhandled-syntax
    }

  static
  //#logging-fsm
  public class MyFSM extends AbstractLoggingFSM<StateType, Data> {
    //#body-elided
    //#logging-fsm
    ActorRef target = null;
    //#logging-fsm
    @Override
    public int logDepth() { return 12; }
    {
      onTermination(
        matchStop(Failure.class, (reason, state, data) -> {
          String lastEvents = getLog().mkString("\n\t");
          log().warning("Failure in state " + state + " with data " + data + "\n" +
            "Events leading up to this point:\n\t" + lastEvents);
          //#logging-fsm
          target.tell(reason.cause(), getSelf());
          target.tell(state, getSelf());
          target.tell(data, getSelf());
          target.tell(lastEvents, getSelf());
          //#logging-fsm
        })
      );
      //...
      //#logging-fsm
      startWith(SomeState, Data.Foo);
      when(SomeState, matchEvent(ActorRef.class, Data.class, (ref, data) -> {
        target = ref;
        target.tell("going active", getSelf());
        return goTo(Active);
      }));
      when(Active, matchEventEquals("stop", (event, data) -> {
        target.tell("stopping", getSelf());
        return stop(new Failure("This is not the error you're looking for"));
      }));
      initialize();
      //#logging-fsm
    }
    //#body-elided
  }
  //#logging-fsm

  @Test
  public void testLoggingFSM()
  {
    new TestKit(system) {{
      final ActorRef logger =
        system.actorOf(Props.create(MyFSM.class));
      final ActorRef probe = getRef();

      logger.tell(probe, probe);
      expectMsgEquals("going active");
      logger.tell("stop", probe);
      expectMsgEquals("stopping");
      expectMsgEquals("This is not the error you're looking for");
      expectMsgEquals(Active);
      expectMsgEquals(Data.Foo);
      String msg = expectMsgClass(String.class);
      assertTrue(msg.startsWith("LogEntry(SomeState,Foo,Actor[akka://FSMDocTest/system/"));
    }};
  }
}
