content: /*
   * Copyright (C) 2015 Actor LLC. <https://actor.im>
   */
  
  package im.actor.runtime.actors;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  
  import im.actor.runtime.actors.dispatch.Mailbox;
  import im.actor.runtime.actors.messages.DeadLetter;
  import im.actor.runtime.threading.SimpleDispatcher;
  import im.actor.runtime.threading.ThreadDispatcher;
  
  /**
   * Actor object
   */
  public class Actor {
  
      private final SimpleDispatcher dispatcher = runnable -> self().post(runnable);
  
      private Scheduler scheduler;
  
      private String path;
  
      private ActorContext context;
      private Mailbox mailbox;
  
      private ArrayList<Receiver> receivers;
      private HashMap<Integer, ArrayList<StashedMessage>> stashed;
  
      public Actor() {
  
      }
  
      /**
       * <p>INTERNAL API</p>
       */
      public SimpleDispatcher getDispatcher() {
          return dispatcher;
      }
  
      /**
       * <p>INTERNAL API</p>
       * Initialization of actor
       *
       * @param path    path of actor
       * @param context context of actor
       * @param mailbox mailbox of actor
       */
      public final void initActor(String path, ActorContext context, Mailbox mailbox) {
          this.path = path;
          this.context = context;
          this.mailbox = mailbox;
      }
  
      /**
       * <p>INTERNAL API</p>
       * Handling of a message in Actor
       *
       * @param message message
       */
      public final void handleMessage(Object message, ActorRef sender) {
          intHandle(message, sender);
      }
  
      public void stash() {
          stash(0);
      }
  
      public void stash(int index) {
          if (stashed == null) {
              stashed = new HashMap<>();
          }
  
          ArrayList<StashedMessage> stashedMessages = stashed.get(index);
          if (stashedMessages == null) {
              stashedMessages = new ArrayList<>();
              stashed.put(index, stashedMessages);
          }
          stashedMessages.add(new StashedMessage(context.message(), context.sender()));
      }
  
      public void unstashAll() {
          unstashAll(0);
      }
  
      public void unstashAll(int index) {
          if (stashed == null) {
              return;
          }
          ArrayList<StashedMessage> stashedMessages = stashed.get(index);
          if (stashedMessages == null || stashedMessages.size() == 0) {
              return;
          }
          StashedMessage stashedMessage;
          for (int i = stashedMessages.size() - 1; i >= 0; i--) {
              stashedMessage = stashedMessages.get(i);
              self().sendFirst(stashedMessage.getMessage(), stashedMessage.getSender());
          }
          stashedMessages.clear();
      }
  
      public void become(Receiver receiver) {
          if (receivers == null) {
              receivers = new ArrayList<>();
          }
          receivers.add(receiver);
      }
  
      public void unbecome() {
          if (receivers == null) {
              receivers = new ArrayList<>();
          }
          receivers.remove(receivers.size() - 1);
      }
  
      private void intHandle(Object message, ActorRef sender) {
          ThreadDispatcher.pushDispatcher(dispatcher);
          context.setSender(sender);
          context.setMessage(message);
          try {
  
              if (receivers != null && receivers.size() > 0) {
                  receivers.get(receivers.size() - 1).onReceive(message);
                  return;
              }
  
              if (message instanceof Runnable) {
                  ((Runnable) message).run();
                  return;
              }
  
              onReceive(message);
  
          } finally {
              ThreadDispatcher.popDispatcher();
              context.setSender(null);
              context.setMessage(null);
          }
      }
  
      /**
       * Actor System
       *
       * @return Actor System
       */
      public final ActorSystem system() {
          return context.getSystem();
      }
  
      /**
       * Self actor reference
       *
       * @return self reference
       */
      public final ActorRef self() {
          return context.getSelf();
      }
  
  //    /**
  //     * Actor context
  //     *
  //     * @return context
  //     */
  //    protected final ActorContext context() {
  //        return context;
  //    }
  
      /**
       * Sender of last received message
       *
       * @return sender's ActorRef
       */
      public final ActorRef sender() {
          return context.sender();
      }
  
      /**
       * Actor path
       *
       * @return path
       */
      protected final String getPath() {
          return path;
      }
  
      /**
       * Actor mailbox
       *
       * @return mailbox
       */
      public final Mailbox getMailbox() {
          return mailbox;
      }
  
      /**
       * Called before first message receiving
       */
      public void preStart() {
  
      }
  
      /**
       * Receiving of message
       *
       * @param message message
       */
      public void onReceive(Object message) {
          drop(message);
      }
  
      /**
       * Called after actor shutdown
       */
      public void postStop() {
  
      }
  
      /**
       * Reply message to sender of last message
       *
       * @param message reply message
       */
      public void reply(Object message) {
          if (context.sender() != null) {
              context.sender().send(message, self());
          }
      }
  
      /**
       * Dropping of message
       *
       * @param message message for dropping
       */
      public void drop(Object message) {
          if (system().getTraceInterface() != null) {
              system().getTraceInterface().onDrop(sender(), message, this);
          }
          reply(new DeadLetter(message));
      }
  
      public void forward(ActorRef dest) {
          dest.send(context.message(), context.sender());
      }
  
      public void halt(String message) {
          halt(message, null);
      }
  
      public void halt(String message, Exception e) {
          throw new ActorHalterException(message, e);
      }
  
  //    public <T> Promise<T> ask(final ActorRef dest, final AskMessage<T> msg) {
  //        return new Promise<T>(new PromiseFunc<T>() {
  //            @Override
  //            public void exec(PromiseResolver<T> executor) {
  //                dest.send(new AskIntRequest(msg, executor));
  //            }
  //        });
  //    }
  //
  //    public void ask(ActorRef dest, Object message) {
  //        ask(dest, message, null);
  //    }
  //
  //    public void ask(final ActorRef dest, final Object message, final AskCallback callback) {
  //        new Promise<>(new PromiseFunc<Object>() {
  //            @Override
  //            public void exec(@NonNull final PromiseResolver<Object> executor) {
  //                become(new Receiver() {
  //                    @Override
  //                    public void onReceive(Object message) {
  //                        if (message instanceof PromiseDispatch) {
  //                            PromiseDispatch dispatch = (PromiseDispatch) message;
  //                            if (dispatch.getPromise() == executor.getPromise()) {
  //                                dispatch.run();
  //                            } else {
  //                                stash();
  //                            }
  //                        } else {
  //                            stash();
  //                        }
  //                    }
  //                });
  //                dest.send(new AskIntRequest(message, executor));
  //            }
  //        }).then(new Consumer<Object>() {
  //            @Override
  //            public void apply(Object o) {
  //                unbecome();
  //                unstashAll();
  //
  //                if (callback != null) {
  //                    callback.onResult(o);
  //                }
  //            }
  //        }).failure(new Consumer<Exception>() {
  //            @Override
  //            public void apply(Exception e) {
  //                unbecome();
  //                unstashAll();
  //
  //                if (callback != null) {
  //                    callback.onError(e);
  //                }
  //            }
  //        }).done(self());
  //    }
  
      public ActorCancellable schedule(final Object obj, long delay) {
          if (scheduler == null) {
              scheduler = new Scheduler(self());
          }
          if (obj instanceof Runnable) {
              return scheduler.schedule((Runnable) obj, delay);
          } else {
              return scheduler.schedule(() -> handleMessage(obj, self()), delay);
          }
      }
  }