content: package im.actor.core.modules.calls;
  
  import java.util.HashMap;
  import java.util.HashSet;
  
  import im.actor.core.entity.Peer;
  import im.actor.core.modules.ModuleContext;
  import im.actor.core.modules.calls.peers.AbsCallActor;
  import im.actor.core.modules.ModuleActor;
  import im.actor.core.providers.CallsProvider;
  import im.actor.core.util.RandomUtils;
  import im.actor.core.viewmodel.CommandCallback;
  import im.actor.runtime.*;
  import im.actor.runtime.Runtime;
  import im.actor.runtime.actors.Actor;
  import im.actor.runtime.actors.ActorCreator;
  import im.actor.runtime.actors.ActorRef;
  import im.actor.runtime.actors.messages.PoisonPill;
  import im.actor.runtime.function.Constructor;
  import im.actor.runtime.power.WakeLock;
  
  public class CallManagerActor extends ModuleActor {
  
      public static ActorCreator CONSTRUCTOR(final ModuleContext context) {
          return () -> new CallManagerActor(context);
      }
  
      private static final String TAG = "CallManagerActor";
  
      private CallsProvider provider;
      private HashSet<Long> handledCalls = new HashSet<>();
      private HashMap<Long, Integer> handledCallAttempts = new HashMap<>();
      private HashSet<Long> answeredCalls = new HashSet<>();
  
      private Long currentCall;
      private HashMap<Long, ActorRef> runningCalls = new HashMap<>();
      private boolean isBeeping = false;
  
      public CallManagerActor(ModuleContext context) {
          super(context);
      }
  
      @Override
      public void preStart() {
          super.preStart();
          provider = config().getCallsProvider();
      }
  
  
      //
      // Outgoing call
      //
  
      private void doCall(final Peer peer, final CommandCallback<Long> callback, boolean isVideoEnabled) {
          Log.d(TAG, "doCall (" + peer + ")");
  
          //
          // Stopping current call as we started new done
          //
          if (currentCall != null) {
              terminalCall(currentCall);
              currentCall = null;
          }
  
          //
          // Spawning new Actor for call
          //
          final WakeLock wakeLock = Runtime.makeWakeLock();
          system().actorOf("actor/master/" + RandomUtils.nextRid(), () -> {
              return new CallActor(peer, callback, wakeLock, isVideoEnabled, context());
          });
      }
  
      private void onCallCreated(long callId, ActorRef ref) {
  
          //
          // Stopping current call some are started during call establishing
          //
          if (currentCall != null) {
              terminalCall(currentCall);
              currentCall = null;
  
              if (isBeeping) {
                  isBeeping = false;
                  provider.stopOutgoingBeep();
              }
          }
  
          //
          // Saving Reference to call
          //
          runningCalls.put(callId, ref);
  
          //
          // Marking outgoing call as answered
          //
          answeredCalls.add(callId);
  
          //
          // Setting Current Call
          //
          currentCall = callId;
  
          //
          // Notify Provider about new current call
          //
          provider.onCallStart(callId);
          isBeeping = true;
          provider.startOutgoingBeep();
      }
  
  
      //
      // Incoming call
      //
  
      private void onIncomingCall(final long callId, final int attempt, WakeLock wakeLock) {
          Log.d(TAG, "onIncomingCall (" + callId + ")");
  
          //
          // Filter double updates about incoming call
          //
          if (handledCalls.contains(callId)) {
              if (handledCallAttempts.get(callId) >= attempt) {
                  if (wakeLock != null) {
                      wakeLock.releaseLock();
                  }
                  return;
              }
          }
  
          //
          // Ignore any incoming call if we already have running call with such call id
          //
          if (runningCalls.containsKey(callId)) {
              if (wakeLock != null) {
                  wakeLock.releaseLock();
              }
              return;
          }
  
          //
          // Marking handled calls as handled
          //
          handledCalls.add(callId);
          handledCallAttempts.put(callId, attempt);
  
          //
          // Creating wake lock if needed
          //
          if (wakeLock == null) {
              wakeLock = Runtime.makeWakeLock();
          }
  
          //
          // Spawning new Actor for call
          //
          final WakeLock finalWakeLock = wakeLock;
          system().actorOf("actor/call" + RandomUtils.nextRid(), () -> {
              return new CallActor(callId, finalWakeLock, context());
          });
      }
  
      private void onIncomingCallReady(long callId, ActorRef ref) {
  
          //
          // Saving reference to incoming call
          //
          runningCalls.put(callId, ref);
  
          //
          // Change Current Call if there are no ongoing calls now
          //
          if (currentCall == null) {
              currentCall = callId;
              provider.onCallStart(callId);
          }
      }
  
      private void onIncomingCallHandled(long callId) {
  
          // If We are not answered this call on this device
          if (!answeredCalls.contains(callId)) {
  
              //
              // Notify provider
              //
              if (currentCall != null && currentCall == callId) {
                  currentCall = null;
                  provider.onCallEnd(callId);
              }
  
              //
              // Shutdown call actor
              //
              terminalCall(callId);
          }
      }
  
      private void doAnswerCall(final long callId) {
          Log.d(TAG, "doAnswerCall (" + callId + ")");
  
          // If not already answered
          if (!answeredCalls.contains(callId)) {
  
              //
              // Mark as answered
              //
              answeredCalls.add(callId);
  
              //
              // Sending answer message to actor.
              //
              ActorRef ref = runningCalls.get(callId);
              if (ref != null) {
                  ref.send(new CallActor.AnswerCall());
              }
  
              //
              // Notify Provider to stop playing ringtone
              //
              if (currentCall != null && currentCall == callId) {
                  provider.onCallAnswered(callId);
              }
          }
      }
  
      private void onCallAnswered(long callId) {
          Log.d(TAG, "onCallAnswered (" + callId + ")");
          if (currentCall == callId) {
              if (isBeeping) {
                  isBeeping = false;
                  provider.stopOutgoingBeep();
              }
  
              provider.onCallAnswered(callId);
          }
      }
  
      //
      // Call AudioEnabled/Unmute
      //
      private void onCallAudioEnable(long callId) {
          ActorRef ref = runningCalls.get(callId);
          if (ref != null) {
              ref.send(new AbsCallActor.AudioEnabled(true));
          }
      }
  
      private void onCallAudioDisable(long callId) {
          ActorRef ref = runningCalls.get(callId);
          if (ref != null) {
              ref.send(new AbsCallActor.AudioEnabled(false));
          }
      }
  
      //
      // Call video disable/enable
      //
      private void onCallVideoEnable(long callId) {
          ActorRef ref = runningCalls.get(callId);
          if (ref != null) {
              ref.send(new AbsCallActor.VideoEnabled(true));
          }
      }
  
      private void onCallVideoDisable(long callId) {
          ActorRef ref = runningCalls.get(callId);
          if (ref != null) {
              ref.send(new AbsCallActor.VideoEnabled(false));
          }
      }
  
      //
      // Ending call
      //
  
      private void onCallEnded(long callId) {
          Log.d(TAG, "onCallEnded (" + callId + ")");
  
          //
          // Event ALWAYS comes from Call Actor and we doesn't need
          // to stop it explicitly.
          //
          // Removing from running calls
          //
          runningCalls.remove(callId);
  
          //
          // Notify Provider if this call was current
          //
          if (currentCall != null && currentCall == callId) {
              currentCall = null;
              provider.onCallEnd(callId);
  
              if (isBeeping) {
                  isBeeping = false;
                  provider.stopOutgoingBeep();
              }
          }
      }
  
      private void doEndCall(long callId) {
          Log.d(TAG, "doEndCall (" + callId + ")");
  
          //
          // Action ALWAYS comes from UI side and we need only stop call actor
          // explicitly and it will do the rest.
          //
          ActorRef currentCallActor = runningCalls.remove(callId);
          if (currentCallActor != null) {
              if (answeredCalls.contains(callId)) {
                  currentCallActor.send(PoisonPill.INSTANCE);
              } else {
                  currentCallActor.send(new CallActor.RejectCall());
              }
          }
  
          //
          // Notify Provider if this call was current
          //
          if (currentCall != null && currentCall == callId) {
              currentCall = null;
              provider.onCallEnd(callId);
              if (isBeeping) {
                  isBeeping = false;
                  provider.stopOutgoingBeep();
              }
          }
      }
  
      private void probablyEndCall() {
          if (currentCall != null) {
              doEndCall(currentCall);
          }
      }
  
      private void terminalCall(long callId) {
          ActorRef dest = runningCalls.remove(callId);
          if (dest != null) {
              dest.send(PoisonPill.INSTANCE);
          }
      }
  
      private void sendToCall(long callId, Object message) {
          ActorRef dest = runningCalls.get(callId);
          if (dest != null) {
              dest.send(message);
          }
      }
  
  
      //
      // Messages
      //
  
      @Override
      public void onReceive(Object message) {
          if (message instanceof OnIncomingCall) {
              OnIncomingCall call = (OnIncomingCall) message;
              onIncomingCall(call.getCallId(), call.getAttempt(), null);
          } else if (message instanceof OnIncomingCallLocked) {
              OnIncomingCallLocked locked = (OnIncomingCallLocked) message;
              onIncomingCall(locked.getCallId(), locked.getAttempt(), locked.getWakeLock());
          } else if (message instanceof OnIncomingCallHandled) {
              OnIncomingCallHandled incomingCallHandled = (OnIncomingCallHandled) message;
              onIncomingCallHandled(incomingCallHandled.getCallId());
          } else if (message instanceof DoAnswerCall) {
              doAnswerCall(((DoAnswerCall) message).getCallId());
          } else if (message instanceof DoEndCall) {
              doEndCall(((DoEndCall) message).getCallId());
          } else if (message instanceof OnCallEnded) {
              onCallEnded(((OnCallEnded) message).getCallId());
          } else if (message instanceof DoCall) {
              DoCall doCall = (DoCall) message;
              doCall(doCall.getPeer(), doCall.getCallback(), doCall.isEnableVideoCall());
          } else if (message instanceof DoCallComplete) {
              DoCallComplete callCreated = (DoCallComplete) message;
              onCallCreated(callCreated.getCallId(), sender());
          } else if (message instanceof IncomingCallReady) {
              IncomingCallReady callComplete = (IncomingCallReady) message;
              onIncomingCallReady(callComplete.getCallId(), sender());
          } else if (message instanceof OnCallAnswered) {
              OnCallAnswered answered = (OnCallAnswered) message;
              onCallAnswered(answered.getCallId());
          } else if (message instanceof AudioDisable) {
              onCallAudioDisable(((AudioDisable) message).getCallId());
          } else if (message instanceof AudioEnable) {
              onCallAudioEnable(((AudioEnable) message).getCallId());
          } else if (message instanceof DisableVideo) {
              onCallVideoDisable(((DisableVideo) message).getCallId());
          } else if (message instanceof EnableVideo) {
              onCallVideoEnable(((EnableVideo) message).getCallId());
          } else if (message instanceof ProbablyEndCall) {
              probablyEndCall();
          } else {
              super.onReceive(message);
          }
      }
  
      public static class OnIncomingCall {
  
          private long callId;
          private int attempt;
  
          public OnIncomingCall(long callId, int attempt) {
              this.callId = callId;
              this.attempt = attempt;
          }
  
          public long getCallId() {
              return callId;
          }
  
          public int getAttempt() {
              return attempt;
          }
      }
  
      public static class OnIncomingCallLocked {
  
          private long callId;
          private int attempt;
          private WakeLock wakeLock;
  
          public OnIncomingCallLocked(long callId, int attempt, WakeLock wakeLock) {
              this.callId = callId;
              this.wakeLock = wakeLock;
              this.attempt = attempt;
          }
  
          public long getCallId() {
              return callId;
          }
  
          public WakeLock getWakeLock() {
              return wakeLock;
          }
  
          public int getAttempt() {
              return attempt;
          }
      }
  
      public static class OnIncomingCallHandled {
  
          private long callId;
          private int attempt;
  
          public OnIncomingCallHandled(long callId, int attempt) {
              this.callId = callId;
              this.attempt = attempt;
          }
  
          public int getAttempt() {
              return attempt;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
      public static class OnCallEnded {
          private long callId;
  
          public OnCallEnded(long callId) {
              this.callId = callId;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
      public static class DoAnswerCall {
  
          private long callId;
  
          public DoAnswerCall(long callId) {
              this.callId = callId;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
      public static class DoEndCall {
          private long callId;
  
          public DoEndCall(long callId) {
              this.callId = callId;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
  
      //
      // Call State
      //
  
      public static class AudioEnable {
          private long callId;
  
          public AudioEnable(long callId) {
              this.callId = callId;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
      public static class AudioDisable {
          private long callId;
  
          public AudioDisable(long callId) {
              this.callId = callId;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
      public static class DisableVideo {
          private long callId;
  
          public DisableVideo(long callId) {
              this.callId = callId;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
      public static class EnableVideo {
          private long callId;
  
          public EnableVideo(long callId) {
              this.callId = callId;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
      //
      // Call Start
      //
  
      public static class DoCall {
  
          private Peer peer;
          private CommandCallback<Long> callback;
          private boolean enableVideoCall;
  
          public DoCall(Peer peer, CommandCallback<Long> callback, boolean enableVideoCall) {
              this.peer = peer;
              this.callback = callback;
              this.enableVideoCall = enableVideoCall;
          }
  
          public CommandCallback<Long> getCallback() {
              return callback;
          }
  
          public Peer getPeer() {
              return peer;
          }
  
          public boolean isEnableVideoCall() {
              return enableVideoCall;
          }
      }
  
      public static class DoCallComplete {
  
          private long callId;
  
          public DoCallComplete(long callId) {
              this.callId = callId;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
      public static class OnCallAnswered {
          private long callId;
  
          public OnCallAnswered(long callId) {
              this.callId = callId;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
      public static class IncomingCallReady {
          private long callId;
  
          public IncomingCallReady(long callId) {
              this.callId = callId;
          }
  
          public long getCallId() {
              return callId;
          }
      }
  
      public static class ProbablyEndCall {
  
      }
  }
  