content: package im.actor.core.modules.messaging.router;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.List;
  
  import im.actor.core.api.ApiDialogGroup;
  import im.actor.core.api.ApiDialogShort;
  import im.actor.core.api.ApiMessageReaction;
  import im.actor.core.api.rpc.RequestLoadGroupedDialogs;
  import im.actor.core.api.updates.UpdateChatClear;
  import im.actor.core.api.updates.UpdateChatDelete;
  import im.actor.core.api.updates.UpdateChatDropCache;
  import im.actor.core.api.updates.UpdateChatGroupsChanged;
  import im.actor.core.api.updates.UpdateMessage;
  import im.actor.core.api.updates.UpdateMessageContentChanged;
  import im.actor.core.api.updates.UpdateMessageDelete;
  import im.actor.core.api.updates.UpdateMessageRead;
  import im.actor.core.api.updates.UpdateMessageReadByMe;
  import im.actor.core.api.updates.UpdateMessageReceived;
  import im.actor.core.api.updates.UpdateMessageSent;
  import im.actor.core.api.updates.UpdateReactionsUpdate;
  import im.actor.core.entity.Avatar;
  import im.actor.core.entity.ContentDescription;
  import im.actor.core.entity.ConversationState;
  import im.actor.core.entity.Group;
  import im.actor.core.entity.Message;
  import im.actor.core.entity.MessageState;
  import im.actor.core.entity.Peer;
  import im.actor.core.entity.PeerType;
  import im.actor.core.entity.Reaction;
  import im.actor.core.entity.User;
  import im.actor.core.entity.content.AbsContent;
  import im.actor.core.entity.content.TextContent;
  import im.actor.core.modules.AbsModule;
  import im.actor.core.modules.ModuleActor;
  import im.actor.core.modules.ModuleContext;
  import im.actor.core.modules.api.ApiSupportConfiguration;
  import im.actor.core.modules.messaging.actions.CursorReaderActor;
  import im.actor.core.modules.messaging.actions.CursorReceiverActor;
  import im.actor.core.modules.messaging.actions.SenderActor;
  import im.actor.core.modules.messaging.dialogs.DialogsInt;
  import im.actor.core.modules.messaging.history.entity.DialogHistory;
  import im.actor.core.modules.messaging.router.entity.ActiveDialogGroup;
  import im.actor.core.modules.messaging.router.entity.ActiveDialogStorage;
  import im.actor.core.modules.messaging.router.entity.RouterAppHidden;
  import im.actor.core.modules.messaging.router.entity.RouterAppVisible;
  import im.actor.core.modules.messaging.router.entity.RouterApplyChatHistory;
  import im.actor.core.modules.messaging.router.entity.RouterApplyDialogsHistory;
  import im.actor.core.modules.messaging.router.entity.RouterChangedContent;
  import im.actor.core.modules.messaging.router.entity.RouterConversationHidden;
  import im.actor.core.modules.messaging.router.entity.RouterConversationVisible;
  import im.actor.core.modules.messaging.router.entity.RouterDeletedMessages;
  import im.actor.core.modules.messaging.router.entity.RouterDifferenceEnd;
  import im.actor.core.modules.messaging.router.entity.RouterDifferenceStart;
  import im.actor.core.modules.messaging.router.entity.RouterMessageOnlyActive;
  import im.actor.core.modules.messaging.router.entity.RouterMessageUpdate;
  import im.actor.core.modules.messaging.router.entity.RouterNewMessages;
  import im.actor.core.modules.messaging.router.entity.RouterOutgoingError;
  import im.actor.core.modules.messaging.router.entity.RouterOutgoingMessage;
  import im.actor.core.modules.messaging.router.entity.RouterOutgoingSent;
  import im.actor.core.modules.messaging.router.entity.RouterPeersChanged;
  import im.actor.core.modules.messaging.router.entity.RouterResetChat;
  import im.actor.core.network.parser.Update;
  import im.actor.core.util.JavaUtil;
  import im.actor.core.viewmodel.DialogGroup;
  import im.actor.core.viewmodel.DialogSmall;
  import im.actor.core.viewmodel.generics.ArrayListDialogSmall;
  import im.actor.runtime.Log;
  import im.actor.runtime.actors.messages.Void;
  import im.actor.runtime.promise.Promise;
  import im.actor.runtime.storage.KeyValueEngine;
  import im.actor.runtime.storage.ListEngine;
  
  import static im.actor.core.entity.EntityConverter.convert;
  import static im.actor.core.util.AssertUtils.assertTrue;
  
  public class RouterActor extends ModuleActor {
  
      private static final String TAG = "RouterActor";
  
      // j2objc workaround
      private static final Void DUMB = null;
  
      // Visibility
      private final HashSet<Peer> visiblePeers = new HashSet<>();
      private boolean isAppVisible = false;
  
      // Storage
      private KeyValueEngine<ConversationState> conversationStates;
  
      // Active Dialogs
      private ActiveDialogStorage activeDialogStorage;
  
  
      public RouterActor(ModuleContext context) {
          super(context);
      }
  
      @Override
      public void preStart() {
          super.preStart();
  
          conversationStates = context().getMessagesModule().getConversationStates().getEngine();
  
          //
          // Loading Active Dialogs
          //
          activeDialogStorage = new ActiveDialogStorage();
          byte[] data = context().getStorageModule().getBlobStorage().loadItem(AbsModule.BLOB_DIALOGS_ACTIVE);
          if (data != null) {
              try {
                  activeDialogStorage = new ActiveDialogStorage(data);
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
          if (!activeDialogStorage.isLoaded()) {
              api(new RequestLoadGroupedDialogs(ApiSupportConfiguration.OPTIMIZATIONS))
                      .chain(r -> updates().applyRelatedData(r.getUsers(), r.getGroups()))
                      .chain(r -> updates().loadRequiredPeers(r.getUserPeers(), r.getGroupPeers()))
                      .then(r -> {
                          boolean showArchived = false;
                          boolean showInvite = false;
                          if (r.showArchived() != null) {
                              showArchived = r.showArchived();
                          }
                          if (r.showInvite() != null) {
                              showInvite = r.showInvite();
                          }
                          onActiveDialogsChanged(r.getDialogs(), showArchived, showInvite);
                          context().getConductor().getConductor().onDialogsLoaded();
                      });
          } else {
              notifyActiveDialogsVM();
              context().getConductor().getConductor().onDialogsLoaded();
          }
      }
  
  
      //
      // Active Dialogs
      //
  
      private void onActiveDialogsChanged(List<ApiDialogGroup> dialogs, boolean showArchived, boolean showInvite) {
  
          //
          // Updating Counters
          //
          ArrayList<ConversationState> convStates = new ArrayList<>();
          for (ApiDialogGroup g : dialogs) {
              for (ApiDialogShort s : g.getDialogs()) {
                  Peer peer = convert(s.getPeer());
                  ConversationState state = conversationStates.getValue(peer.getUnuqueId());
                  boolean isChanged = false;
                  if (state.getUnreadCount() != s.getCounter() && !isConversationVisible(peer)) {
                      state = state.changeCounter(s.getCounter());
                      isChanged = true;
                  }
                  if (state.getInMaxMessageDate() < s.getDate()) {
                      state = state.changeInMaxDate(s.getDate());
                      isChanged = true;
                  }
                  if (isChanged) {
                      convStates.add(state);
                  }
              }
          }
          conversationStates.addOrUpdateItems(convStates);
  
          //
          // Updating storage
          //
          activeDialogStorage.setHaveArchived(showArchived);
          activeDialogStorage.setShowInvite(showInvite);
          activeDialogStorage.setLoaded(true);
          activeDialogStorage.getGroups().clear();
          for (ApiDialogGroup g : dialogs) {
              ArrayList<Peer> peers = new ArrayList<>();
              for (ApiDialogShort s : g.getDialogs()) {
                  Peer peer = convert(s.getPeer());
                  peers.add(peer);
              }
              activeDialogStorage.getGroups().add(new ActiveDialogGroup(g.getKey(), g.getTitle(), peers));
          }
          context().getStorageModule().getBlobStorage()
                  .addOrUpdateItem(AbsModule.BLOB_DIALOGS_ACTIVE, activeDialogStorage.toByteArray());
  
          //
          // Notify VM
          //
          notifyActiveDialogsVM();
  
          //
          // Unstash all messages after initial loading
          //
          unstashAll();
      }
  
  
      //
      // Incoming Messages
      //
  
      private Promise<Void> onNewMessages(Peer peer, List<Message> messages) {
  
          assertTrue(messages.size() != 0);
  
          boolean isConversationVisible = isConversationVisible(peer);
  
          //
          // Collecting Information
          //
          ConversationState state = conversationStates.getValue(peer.getUnuqueId());
          Message topMessage = null;
          int unreadCount = 0;
          long maxInReadDate = 0;
          long maxInDate = 0;
          for (Message m : messages) {
              if (topMessage == null || topMessage.getSortDate() < m.getSortDate()) {
                  topMessage = m;
              }
              if (m.getSenderId() != myUid()) {
                  if (m.getSortDate() > state.getInReadDate()) {
                      unreadCount++;
                      maxInReadDate = Math.max(maxInReadDate, m.getSortDate());
                  }
                  if (m.getSortDate() > state.getInMaxMessageDate()) {
                      maxInDate = Math.max(maxInDate, m.getSortDate());
                  }
              }
          }
  
  
          //
          // Writing to Conversation
          //
          conversation(peer).addOrUpdateItems(messages);
  
  
          //
          // Update Chat State
          //
          updateChatState(peer);
  
          //
          // Updating Counter
          //
          boolean isRead = false;
          if (unreadCount != 0) {
              if (isConversationVisible) {
                  // Auto Reading message
                  boolean needUpdateState = false;
                  if (maxInReadDate > 0) {
                      if (state.getInReadDate() < maxInReadDate) {
                          state = state.changeInReadDate(maxInReadDate);
                      }
                      state = state.changeCounter(0);
  
                      context().getMessagesModule().getPlainReadActor()
                              .send(new CursorReaderActor.MarkRead(peer, maxInReadDate));
                      context().getNotificationsModule().onOwnRead(peer, maxInReadDate);
                      isRead = true;
                      needUpdateState = true;
                  }
  
                  if (state.getInMaxMessageDate() < maxInDate) {
                      state.changeInMaxDate(maxInDate);
                      needUpdateState = true;
                  }
  
                  if (needUpdateState) {
                      conversationStates.addOrUpdateItem(state);
                  }
  
              } else {
                  // Updating counter
                  state = state.changeCounter(state.getUnreadCount() + unreadCount);
                  if (state.getInMaxMessageDate() < maxInDate) {
                      state = state
                              .changeInMaxDate(maxInDate);
                  }
                  conversationStates.addOrUpdateItem(state);
  
                  notifyActiveDialogsVM();
              }
          }
  
  
          //
          // Marking As Received
          //
          if (maxInReadDate > 0 && !isRead) {
              context().getMessagesModule().getPlainReceiverActor()
                      .send(new CursorReceiverActor.MarkReceived(peer, maxInReadDate));
          }
  
  
          //
          // Updating Dialog List
          //
          Promise<Void> res = getDialogsRouter().onMessage(peer, topMessage, state.getUnreadCount());
  
  
          //
          // Playing notifications
          //
          if (!isConversationVisible) {
              for (Message m : messages) {
                  if (m.getSenderId() != myUid()) {
                      boolean hasCurrentMention = false;
                      if (m.getContent() instanceof TextContent) {
                          if (((TextContent) m.getContent()).getMentions().contains(myUid())) {
                              hasCurrentMention = true;
                          }
                      }
                      int messagesCount = 0;
                      int dialogsCount = 0;
                      for (Peer activePeer : activeDialogStorage.getAllPeers()) {
                          int activeDialogueUnreadCount = conversationStates.getValue(activePeer.getUnuqueId()).getUnreadCount();
                          if (activeDialogueUnreadCount > 0) {
                              dialogsCount++;
                              messagesCount += activeDialogueUnreadCount;
                          }
                      }
  
                      context().getNotificationsModule().onInMessage(
                              peer,
                              m.getSenderId(),
                              m.getSortDate(),
                              ContentDescription.fromContent(m.getContent()),
                              hasCurrentMention,
                              messagesCount,
                              dialogsCount);
                  }
              }
          }
  
          return res;
      }
  
  
      //
      // Outgoing Messages
      //
  
      private Promise<Void> onOutgoingMessage(Peer peer, Message message) {
          conversation(peer).addOrUpdateItem(message);
          updateChatState(peer);
          return Promise.success(null);
      }
  
      private Promise<Void> onOutgoingSent(Peer peer, long rid, long date) {
          Message msg = conversation(peer).getValue(rid);
          // If we have pending message
          if (msg != null && (msg.getMessageState() == MessageState.PENDING)) {
  
              // Updating message
              Message updatedMsg = msg
                      .changeAllDate(date)
                      .changeState(MessageState.SENT);
              conversation(peer).addOrUpdateItem(updatedMsg);
  
              ConversationState state = conversationStates.getValue(peer.getUnuqueId());
              conversationStates.addOrUpdateItem(state.changeOutSendDate(date));
  
              updateChatState(peer);
  
              // Notify dialogs
              return getDialogsRouter().onMessage(peer, updatedMsg, -1);
          } else {
              return Promise.success(null);
          }
      }
  
      private Promise<Void> onOutgoingError(Peer peer, long rid) {
          Message msg = conversation(peer).getValue(rid);
          // If we have pending message
          if (msg != null && (msg.getMessageState() == MessageState.PENDING)) {
  
              // Updating message
              Message updatedMsg = msg
                      .changeState(MessageState.ERROR);
              conversation(peer).addOrUpdateItem(updatedMsg);
  
              updateChatState(peer);
          }
          return Promise.success(null);
      }
  
  
      //
      // History Messages
      //
  
      private Promise<Void> onDialogHistoryLoaded(List<DialogHistory> dialogs) {
          for (DialogHistory d : dialogs) {
              ConversationState state = conversationStates.getValue(d.getPeer().getUnuqueId());
              if (d.getUnreadCount() > 0) {
                  state = state
                          .changeCounter(d.getUnreadCount())
                          .changeInMaxDate(d.getDate());
              }
              if (d.isRead()) {
                  state = state
                          .changeOutReadDate(d.getDate())
                          .changeOutReceiveDate(d.getDate());
              } else if (d.isReceived()) {
                  state = state
                          .changeOutReceiveDate(d.getDate());
              }
              conversationStates.addOrUpdateItem(state);
          }
  
          return getDialogsRouter().onHistoryLoaded(dialogs);
      }
  
      private Promise<Void> onChatHistoryLoaded(Peer peer, List<Message> messages, Long maxReadDate,
                                                Long maxReceiveDate, boolean isEnded) {
  
          Log.d(TAG, "History Loaded");
  
          long maxMessageDate = 0;
  
          // Processing all new messages
          ArrayList<Message> updated = new ArrayList<>();
          for (Message historyMessage : messages) {
              // Ignore already present messages
              if (conversation(peer).getValue(historyMessage.getEngineId()) != null) {
                  continue;
              }
  
              updated.add(historyMessage);
  
              if (historyMessage.getSenderId() != myUid()) {
                  maxMessageDate = Math.max(maxMessageDate, historyMessage.getSortDate());
              }
          }
  
          // Writing messages
          conversation(peer).addOrUpdateItems(updated);
  
          // Updating conversation state
          ConversationState state = conversationStates.getValue(peer.getUnuqueId());
          boolean isChanged = false;
          if (state.getInMaxMessageDate() < maxMessageDate) {
              state = state.changeInMaxDate(maxMessageDate);
              isChanged = true;
          }
          if (maxReadDate != null && maxReadDate != 0 && state.getOutReadDate() < maxMessageDate) {
              state = state.changeOutReadDate(maxReadDate);
              isChanged = true;
          }
          if (maxReceiveDate != null && maxReceiveDate != 0 && state.getOutReceiveDate() < maxReceiveDate) {
              state = state.changeOutReceiveDate(maxReceiveDate);
              isChanged = true;
          }
          if (state.isLoaded() != isEnded) {
              state = state.changeIsLoaded(isEnded);
              isChanged = true;
          }
          boolean isEmpty = conversation(peer).isEmpty();
          if (state.isEmpty() != isEmpty) {
              state = state.changeIsEmpty(isEmpty);
              isChanged = true;
          }
          if (isChanged) {
              conversationStates.addOrUpdateItem(state);
          }
  
          // Reading messages if needed
          markAsReadIfNeeded(peer);
  
          return Promise.success(null);
      }
  
  
      //
      // Message Updating
      //
  
      private Promise<Void> onContentUpdate(Peer peer, long rid, AbsContent content) {
  
          Message message = conversation(peer).getValue(rid);
  
          if (message != null) {
              conversation(peer).addOrUpdateItem(message.changeContent(content.incrementUpdatedCounter(message.getContent().getUpdatedCounter())));
              return getDialogsRouter().onMessageContentChanged(peer, rid, content);
          } else {
              return Promise.success(null);
          }
      }
  
      private Promise<Void> onReactionsUpdate(Peer peer, long rid, List<Reaction> reactions) {
          Message message = conversation(peer).getValue(rid);
  
          // Ignore if we already doesn't have this message
          if (message != null) {
              conversation(peer).addOrUpdateItem(message.changeReactions(reactions));
          }
  
          return Promise.success(null);
      }
  
  
      //
      // Message Deletions
      //
  
      private Promise<Void> onMessageDeleted(Peer peer, List<Long> rids) {
  
          // Delete Messages
          conversation(peer).removeItems(JavaUtil.unbox(rids));
  
          updateChatState(peer);
  
          Message head = conversation(peer).getHeadValue();
  
          if (head != null) {
              ConversationState state = conversationStates.getValue(peer.getUnuqueId());
              state = state
                      .changeInReadDate(head.getSortDate())
                      .changeOutSendDate(head.getSortDate());
              conversationStates.addOrUpdateItem(state);
  
              if (head.getMessageState() == MessageState.PENDING) {
                  head = null;
              }
          }
  
          return getDialogsRouter().onMessageDeleted(peer, head);
      }
  
      private Promise<Void> onChatClear(Peer peer) {
  
          conversation(peer).clear();
  
          ConversationState state = conversationStates.getValue(peer.getUnuqueId());
          if (!state.isLoaded()) {
              state = state.changeIsLoaded(true);
              conversationStates.addOrUpdateItem(state);
          }
  
          updateChatState(peer);
  
          return getDialogsRouter().onChatClear(peer);
      }
  
      private Promise<Void> onChatDropCache(Peer peer) {
          return context().getMessagesModule().getHistoryActor(peer).reset();
      }
  
      private Promise<Void> onChatReset(Peer peer) {
  
          Log.d(TAG, "onChatReset");
  
          conversation(peer).clear();
  
          ConversationState state = conversationStates.getValue(peer.getUnuqueId());
          state = state.changeIsLoaded(false);
          conversationStates.addOrUpdateItem(state);
  
          updateChatState(peer);
  
          return Promise.success(null);
      }
  
      private Promise<Void> onChatDelete(Peer peer) {
  
          conversation(peer).clear();
  
          ConversationState state = conversationStates.getValue(peer.getUnuqueId());
          if (!state.isLoaded()) {
              state = state.changeIsLoaded(true);
              conversationStates.addOrUpdateItem(state);
          }
  
          updateChatState(peer);
  
          return getDialogsRouter().onChatDelete(peer).chain(aVoid -> onChatDropCache(peer));
      }
  
  
      //
      // Read States
      //
  
      private Promise<Void> onMessageRead(Peer peer, long date) {
          ConversationState state = conversationStates.getValue(peer.getUnuqueId());
          boolean isChanged = false;
          Promise<Void> res;
          if (date > state.getOutReadDate()) {
              state = state.changeOutReadDate(date);
              res = getDialogsRouter().onPeerReadChanged(peer, date);
              isChanged = true;
          } else {
              res = Promise.success(null);
          }
          if (date > state.getOutReceiveDate()) {
              state = state.changeOutReceiveDate(date);
              isChanged = true;
          }
          if (isChanged) {
              conversationStates.addOrUpdateItem(state);
          }
  
          return res;
      }
  
      private Promise<Void> onMessageReceived(Peer peer, long date) {
          ConversationState state = conversationStates.getValue(peer.getUnuqueId());
          if (date > state.getOutReceiveDate()) {
              state = state.changeOutReceiveDate(date);
              conversationStates.addOrUpdateItem(state);
  
              return getDialogsRouter().onPeerReceiveChanged(peer, date);
          } else {
              return Promise.success(null);
          }
      }
  
      private Promise<Void> onMessageReadByMe(Peer peer, long date, int counter) {
  
          ConversationState state = conversationStates.getValue(peer.getUnuqueId());
  
          if (state.getInReadDate() >= date) {
              return Promise.success(null);
          }
  
          state = state
                  .changeCounter(counter)
                  .changeInReadDate(date);
          conversationStates.addOrUpdateItem(state);
  
          Promise<Void> res = getDialogsRouter().onCounterChanged(peer, counter);
  
          notifyActiveDialogsVM();
  
          context().getNotificationsModule().onOwnRead(peer, date);
  
          return res;
      }
  
  
      //
      // Peer Changed
      //
  
      private Promise<Void> onPeersChanged(List<User> users, List<Group> groups) {
  
          Promise<Void> res = Promise.success(null);
  
          boolean isActiveNeedUpdate = false;
          for (User u : users) {
              if (!isActiveNeedUpdate) {
                  for (ActiveDialogGroup g : activeDialogStorage.getGroups()) {
                      if (g.getPeers().contains(u.peer())) {
                          isActiveNeedUpdate = true;
                          break;
                      }
                  }
              }
              res = res.chain(v -> getDialogsRouter().onUserChanged(u));
          }
          for (Group group : groups) {
              if (!isActiveNeedUpdate) {
                  for (ActiveDialogGroup g : activeDialogStorage.getGroups()) {
                      if (g.getPeers().contains(group.peer())) {
                          isActiveNeedUpdate = true;
                          break;
                      }
                  }
              }
  
              res = res.chain(v -> getDialogsRouter().onGroupChanged(group));
          }
  
          if (isActiveNeedUpdate) {
              notifyActiveDialogsVM();
          }
  
          return res;
      }
  
  
      //
      // Auto Messages Read
      //
  
      private void onConversationVisible(Peer peer) {
          visiblePeers.add(peer);
  
          markAsReadIfNeeded(peer);
  
          updateChatState(peer);
      }
  
      private void onConversationHidden(Peer peer) {
          visiblePeers.remove(peer);
      }
  
      private void onAppVisible() {
          isAppVisible = true;
  
          for (Peer p : visiblePeers) {
              markAsReadIfNeeded(p);
          }
      }
  
      private void onAppHidden() {
          isAppVisible = false;
      }
  
      private boolean isConversationVisible(Peer peer) {
          return visiblePeers.contains(peer) && isAppVisible;
      }
  
      private void markAsReadIfNeeded(Peer peer) {
          if (isConversationVisible(peer)) {
              ConversationState state = conversationStates.getValue(peer.getUnuqueId());
              long inMaxMessageDate = state.getInMaxMessageDate();
              //check UnreadCount for zero, because it can be loaded from server (after login)
              if (state.getUnreadCount() != 0 || state.getInReadDate() < inMaxMessageDate) {
                  state = state
                          .changeCounter(0)
                          .changeInReadDate(inMaxMessageDate);
                  conversationStates.addOrUpdateItem(state);
  
                  context().getMessagesModule().getPlainReadActor()
                          .send(new CursorReaderActor.MarkRead(peer, inMaxMessageDate));
  
                  notifyActiveDialogsVM();
  
                  getDialogsRouter().onCounterChanged(peer, 0);
  
                  context().getNotificationsModule().onOwnRead(peer, inMaxMessageDate);
              }
          }
      }
  
      private void updateChatState(Peer peer) {
          boolean isEmpty = conversation(peer).isEmpty();
          ConversationState state = conversationStates.getValue(peer.getUnuqueId());
          if (state.isEmpty() != isEmpty) {
              state = state.changeIsEmpty(isEmpty);
          }
  
          conversationStates.addOrUpdateItem(state);
      }
  
      //
      // Difference Handling
      //
  
      public Promise<Void> onDifferenceStart() {
          context().getNotificationsModule().pauseNotifications();
          return Promise.success(null);
      }
  
      public Promise<Void> onDifferenceEnd() {
          context().getNotificationsModule().resumeNotifications();
          return Promise.success(null);
      }
  
  
      //
      // Tools
      //
  
      private DialogsInt getDialogsRouter() {
          return context().getMessagesModule().getDialogsInt();
      }
  
      private ListEngine<Message> conversation(Peer peer) {
          return context().getMessagesModule().getConversationEngine(peer);
      }
  
      private void notifyActiveDialogsVM() {
          int counter = 0;
          ArrayList<DialogGroup> groups = new ArrayList<>();
          for (ActiveDialogGroup i : activeDialogStorage.getGroups()) {
              ArrayListDialogSmall dialogSmalls = new ArrayListDialogSmall();
              for (Peer p : i.getPeers()) {
                  String title;
                  Avatar avatar;
                  if (p.getPeerType() == PeerType.GROUP) {
                      Group group = getGroup(p.getPeerId());
                      title = group.getTitle();
                      avatar = group.getAvatar();
                  } else if (p.getPeerType() == PeerType.PRIVATE) {
                      User user = getUser(p.getPeerId());
                      title = user.getName();
                      avatar = user.getAvatar();
                  } else {
                      continue;
                  }
  
                  int unreadCount = conversationStates.getValue(p.getUnuqueId()).getUnreadCount();
                  counter += unreadCount;
                  dialogSmalls.add(new DialogSmall(p, title, avatar, unreadCount));
              }
              groups.add(new DialogGroup(i.getTitle(), i.getKey(), dialogSmalls));
          }
          context().getMessagesModule().getDialogGroupsVM().getGroupsValueModel().change(groups);
          context().getConductor().getGlobalStateVM().onGlobalCounterChanged(counter);
      }
  
      public boolean isValidPeer(Peer peer) {
          if (peer.getPeerType() == PeerType.PRIVATE) {
              return users().getValue(peer.getPeerId()) != null;
          } else if (peer.getPeerType() == PeerType.GROUP) {
              return groups().getValue(peer.getPeerId()) != null;
          }
          return false;
      }
  
  
      //
      // Messages
      //
  
      public Promise<Void> onUpdate(Update update) {
          if (update instanceof UpdateMessage) {
              UpdateMessage msg = (UpdateMessage) update;
  
              Peer peer = convert(msg.getPeer());
  
              AbsContent msgContent = AbsContent.fromMessage(msg.getMessage());
  
              Message message = new Message(
                      msg.getRid(),
                      msg.getDate(),
                      msg.getDate(),
                      msg.getSenderUid(),
                      myUid() == msg.getSenderUid() ? MessageState.SENT : MessageState.UNKNOWN,
                      msgContent);
  
              ArrayList<Message> messages = new ArrayList<>();
              messages.add(message);
              return onNewMessages(peer, messages);
          } else if (update instanceof UpdateMessageSent) {
              UpdateMessageSent messageSent = (UpdateMessageSent) update;
              Peer peer = convert(messageSent.getPeer());
              if (isValidPeer(peer)) {
                  // Notify Sender
                  context().getMessagesModule()
                          .getSendMessageActor()
                          .send(new SenderActor.MessageSent(peer, messageSent.getRid()));
                  return onOutgoingSent(
                          peer,
                          messageSent.getRid(),
                          messageSent.getDate());
              }
              return Promise.success(null);
          } else if (update instanceof UpdateMessageRead) {
              UpdateMessageRead read = (UpdateMessageRead) update;
              Peer peer = convert(read.getPeer());
              if (isValidPeer(peer)) {
                  return onMessageRead(peer, read.getStartDate());
              }
              return Promise.success(null);
          } else if (update instanceof UpdateMessageReadByMe) {
              UpdateMessageReadByMe readByMe = (UpdateMessageReadByMe) update;
              Peer peer = convert(readByMe.getPeer());
              if (isValidPeer(peer)) {
                  int counter = 0;
                  if (readByMe.getUnreadCounter() != null) {
                      counter = readByMe.getUnreadCounter();
                  }
                  return onMessageReadByMe(peer, readByMe.getStartDate(), counter);
              }
              return Promise.success(null);
          } else if (update instanceof UpdateMessageReceived) {
              UpdateMessageReceived received = (UpdateMessageReceived) update;
              Peer peer = convert(received.getPeer());
              if (isValidPeer(peer)) {
                  return onMessageReceived(peer, received.getStartDate());
              }
              return Promise.success(null);
          } else if (update instanceof UpdateChatDelete) {
              UpdateChatDelete delete = (UpdateChatDelete) update;
              Peer peer = convert(delete.getPeer());
              if (isValidPeer(peer)) {
                  return onChatDelete(peer);
              }
              return Promise.success(null);
          } else if (update instanceof UpdateChatClear) {
              UpdateChatClear clear = (UpdateChatClear) update;
              Peer peer = convert(clear.getPeer());
              if (isValidPeer(peer)) {
                  return onChatClear(peer);
              }
              return Promise.success(null);
          } else if (update instanceof UpdateChatDropCache) {
              UpdateChatDropCache dropCache = (UpdateChatDropCache) update;
              Peer peer = convert(dropCache.getPeer());
              if (isValidPeer(peer)) {
                  return onChatDropCache(peer);
              }
              return Promise.success(null);
          } else if (update instanceof UpdateChatGroupsChanged) {
              UpdateChatGroupsChanged chatGroupsChanged = (UpdateChatGroupsChanged) update;
              onActiveDialogsChanged(chatGroupsChanged.getDialogs(), true, true);
              return Promise.success(null);
          } else if (update instanceof UpdateMessageDelete) {
              UpdateMessageDelete delete = (UpdateMessageDelete) update;
              Peer peer = convert(delete.getPeer());
              if (isValidPeer(peer)) {
                  return onMessageDeleted(peer, delete.getRids());
              }
              return Promise.success(null);
          } else if (update instanceof UpdateMessageContentChanged) {
              UpdateMessageContentChanged contentChanged = (UpdateMessageContentChanged) update;
              Peer peer = convert(contentChanged.getPeer());
              if (isValidPeer(peer)) {
                  AbsContent content = AbsContent.fromMessage(contentChanged.getMessage());
                  return onContentUpdate(peer, contentChanged.getRid(), content);
              }
              return Promise.success(null);
          } else if (update instanceof UpdateReactionsUpdate) {
              UpdateReactionsUpdate reactionsUpdate = (UpdateReactionsUpdate) update;
              Peer peer = convert(reactionsUpdate.getPeer());
              if (isValidPeer(peer)) {
                  ArrayList<Reaction> reactions = new ArrayList<>();
                  for (ApiMessageReaction r : reactionsUpdate.getReactions()) {
                      reactions.add(new Reaction(r.getCode(), r.getUsers()));
                  }
                  return onReactionsUpdate(peer, reactionsUpdate.getRid(), reactions);
              }
              return Promise.success(null);
          }
  
          return Promise.success(null);
      }
  
      @Override
      public void onReceive(Object message) {
          if (!activeDialogStorage.isLoaded() && message instanceof RouterMessageOnlyActive) {
              stash();
              return;
          }
          if (message instanceof RouterConversationVisible) {
              RouterConversationVisible conversationVisible = (RouterConversationVisible) message;
              onConversationVisible(conversationVisible.getPeer());
          } else if (message instanceof RouterConversationHidden) {
              RouterConversationHidden conversationHidden = (RouterConversationHidden) message;
              onConversationHidden(conversationHidden.getPeer());
          } else if (message instanceof RouterAppVisible) {
              onAppVisible();
          } else if (message instanceof RouterAppHidden) {
              onAppHidden();
          } else {
              super.onReceive(message);
          }
      }
  
      @Override
      public Promise onAsk(Object message) throws Exception {
          if (!activeDialogStorage.isLoaded() && message instanceof RouterMessageOnlyActive) {
              stash();
              return null;
          }
          if (message instanceof RouterMessageUpdate) {
              return onUpdate(((RouterMessageUpdate) message).getUpdate());
          } else if (message instanceof RouterDifferenceStart) {
              return onDifferenceStart();
          } else if (message instanceof RouterDifferenceEnd) {
              return onDifferenceEnd();
          } else if (message instanceof RouterPeersChanged) {
              RouterPeersChanged peersChanged = (RouterPeersChanged) message;
              return onPeersChanged(peersChanged.getUsers(), peersChanged.getGroups());
          } else if (message instanceof RouterApplyChatHistory) {
              RouterApplyChatHistory chatHistory = (RouterApplyChatHistory) message;
              return onChatHistoryLoaded(
                      chatHistory.getPeer(),
                      chatHistory.getMessages(),
                      chatHistory.getMaxReadDate(),
                      chatHistory.getMaxReceiveDate(),
                      chatHistory.isEnded());
          } else if (message instanceof RouterApplyDialogsHistory) {
              RouterApplyDialogsHistory dialogsHistory = (RouterApplyDialogsHistory) message;
              return onDialogHistoryLoaded(dialogsHistory.getDialogs());
          } else if (message instanceof RouterNewMessages) {
              RouterNewMessages routerNewMessages = (RouterNewMessages) message;
              return onNewMessages(
                      routerNewMessages.getPeer(),
                      routerNewMessages.getMessages());
          } else if (message instanceof RouterOutgoingMessage) {
              RouterOutgoingMessage routerOutgoingMessage = (RouterOutgoingMessage) message;
              return onOutgoingMessage(
                      routerOutgoingMessage.getPeer(),
                      routerOutgoingMessage.getMessage());
          } else if (message instanceof RouterOutgoingSent) {
              RouterOutgoingSent routerOutgoingSent = (RouterOutgoingSent) message;
              return onOutgoingSent(
                      routerOutgoingSent.getPeer(),
                      routerOutgoingSent.getRid(),
                      routerOutgoingSent.getDate());
          } else if (message instanceof RouterOutgoingError) {
              RouterOutgoingError outgoingError = (RouterOutgoingError) message;
              return onOutgoingError(
                      outgoingError.getPeer(),
                      outgoingError.getRid());
          } else if (message instanceof RouterChangedContent) {
              RouterChangedContent routerChangedContent = (RouterChangedContent) message;
              return onContentUpdate(
                      routerChangedContent.getPeer(),
                      routerChangedContent.getRid(),
                      routerChangedContent.getContent());
          } else if (message instanceof RouterDeletedMessages) {
              RouterDeletedMessages routerDeletedMessages = (RouterDeletedMessages) message;
              return onMessageDeleted(
                      routerDeletedMessages.getPeer(),
                      routerDeletedMessages.getRids());
          } else if (message instanceof RouterResetChat) {
              RouterResetChat resetChat = (RouterResetChat) message;
              return onChatReset(resetChat.getPeer());
          } else {
              return super.onAsk(message);
          }
      }
  }
  