content: /*
   * Copyright (C) 2015 Actor LLC. <https://actor.im>
   */
  
  package im.actor.core.js.modules;
  
  import com.google.gwt.core.client.JavaScriptObject;
  import com.google.gwt.core.client.JsArray;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.HashSet;
  
  import im.actor.core.entity.Avatar;
  import im.actor.core.entity.Contact;
  import im.actor.core.entity.Dialog;
  import im.actor.core.entity.Message;
  import im.actor.core.entity.Peer;
  import im.actor.core.entity.PeerType;
  import im.actor.core.entity.SearchEntity;
  import im.actor.core.entity.Sticker;
  import im.actor.core.entity.StickerPack;
  import im.actor.core.entity.content.DocumentContent;
  import im.actor.core.entity.content.FileRemoteSource;
  import im.actor.core.entity.content.StickerContent;
  import im.actor.core.js.JsMessenger;
  import im.actor.core.js.entity.JsBlockedUser;
  import im.actor.core.js.entity.JsCall;
  import im.actor.core.js.entity.JsContact;
  import im.actor.core.js.entity.JsCounter;
  import im.actor.core.js.entity.JsDialog;
  import im.actor.core.js.entity.JsDialogGroup;
  import im.actor.core.js.entity.JsDialogShort;
  import im.actor.core.js.entity.JsEventBusCallback;
  import im.actor.core.js.entity.JsGroup;
  import im.actor.core.js.entity.JsMessage;
  import im.actor.core.js.entity.JsOnlineGroup;
  import im.actor.core.js.entity.JsOnlineUser;
  import im.actor.core.js.entity.JsSearchEntity;
  import im.actor.core.js.entity.JsSticker;
  import im.actor.core.js.entity.JsTyping;
  import im.actor.core.js.entity.JsUser;
  import im.actor.core.modules.AbsModule;
  import im.actor.core.modules.Modules;
  import im.actor.core.viewmodel.AppStateVM;
  import im.actor.core.viewmodel.CallMember;
  import im.actor.core.viewmodel.CallState;
  import im.actor.core.viewmodel.CallVM;
  import im.actor.core.viewmodel.DialogGroup;
  import im.actor.core.viewmodel.DialogSmall;
  import im.actor.core.viewmodel.GlobalStateVM;
  import im.actor.core.viewmodel.GroupTypingVM;
  import im.actor.core.viewmodel.GroupVM;
  import im.actor.core.viewmodel.UserPresence;
  import im.actor.core.viewmodel.UserTypingVM;
  import im.actor.core.viewmodel.UserVM;
  import im.actor.core.viewmodel.generics.ArrayListMediaTrack;
  import im.actor.runtime.js.mvvm.JsDisplayList;
  import im.actor.runtime.js.mvvm.JsDisplayListBind;
  import im.actor.runtime.mvvm.ModelChangedListener;
  import im.actor.runtime.mvvm.Value;
  import im.actor.runtime.mvvm.ValueChangedListener;
  import im.actor.runtime.mvvm.ValueModel;
  import im.actor.runtime.webrtc.WebRTCMediaStream;
  
  public class JsBindingModule extends AbsModule implements JsFileLoadedListener {
  
      private JsMessenger messenger;
  
      private HashMap<Integer, JsBindedValue<JsUser>> users = new HashMap<>();
      private HashMap<Integer, JsBindedValue<JsGroup>> groups = new HashMap<>();
      private HashMap<Integer, JsBindedValue<JsOnlineUser>> usersOnlines = new HashMap<>();
      private HashMap<Integer, JsBindedValue<JsBlockedUser>> usersBloked = new HashMap<>();
      private HashMap<Integer, JsBindedValue<JsOnlineGroup>> groupOnlines = new HashMap<>();
      private HashMap<Peer, JsBindedValue<JsTyping>> typing = new HashMap<>();
      private JsBindedValue<String> onlineState;
  
      private JsDisplayList<JsDialog, Dialog> dialogsList;
      private JsDisplayList<JsContact, Contact> contactsList;
      private JsDisplayList<JsSearchEntity, SearchEntity> searchList;
      private HashMap<Peer, JsDisplayList<JsMessage, Message>> messageLists = new HashMap<>();
  
      private JsBindedValue<JsCounter> globalCounter;
      private JsBindedValue<JsCounter> tempGlobalCounter;
  
      private JsBindedValue<JsArray<JsDialogGroup>> dialogsGroupedList;
  
      private HashMap<String, JsBindedValue<JsCall>> calls = new HashMap<>();
  
      private ArrayList<JsEventBusCallback> eventBusCallbacks = new ArrayList<>();
  
      private JsBindedValue<JsArray<JsSticker>> stickers;
  
      public JsBindingModule(JsMessenger messenger, JsFilesModule filesModule, Modules modules) {
          super(modules);
  
          this.messenger = messenger;
          filesModule.registerListener(this);
      }
  
      public void broadcastEvent(String tag, JavaScriptObject obj) {
          for (JsEventBusCallback c : eventBusCallbacks) {
              c.onEvent(tag, obj);
          }
      }
  
      public void subscribeEventBus(JsEventBusCallback callback) {
          if (!eventBusCallbacks.contains(callback)) {
              eventBusCallbacks.add(callback);
          }
      }
  
      public void unsubscribeEventBus(JsEventBusCallback callback) {
          eventBusCallbacks.remove(callback);
      }
  
      public JsBindedValue<JsArray<JsDialogGroup>> getDialogsGroupedList() {
          if (dialogsGroupedList == null) {
              ValueModel<ArrayList<DialogGroup>> dialogGroups =
                      context().getMessagesModule().getDialogGroupsVM().getGroupsValueModel();
              dialogsGroupedList = new JsBindedValue<>();
              dialogGroups.subscribe(new ValueChangedListener<ArrayList<DialogGroup>>() {
                  @Override
                  public void onChanged(ArrayList<DialogGroup> val, Value<ArrayList<DialogGroup>> valueModel) {
                      if (val == null) {
                          dialogsGroupedList.changeValue(JsArray.createArray().<JsArray<JsDialogGroup>>cast());
                      } else {
                          JsArray<JsDialogGroup> res = JsArray.createArray().cast();
                          for (DialogGroup g : val) {
                              JsArray<JsDialogShort> resd = JsArray.createArray().cast();
                              for (DialogSmall ds : g.getDialogs()) {
                                  resd.push(JsDialogShort.create(messenger.buildPeerInfo(ds.getPeer()), ds.getCounter()));
                              }
                              res.push(JsDialogGroup.create(g.getTitle(), g.getKey(), resd));
                          }
                          dialogsGroupedList.changeValue(res);
                      }
                  }
              });
          }
  
          return dialogsGroupedList;
      }
  
      public JsBindedValue<JsArray<JsSticker>> getStickers() {
          if (stickers == null) {
              stickers = new JsBindedValue<>((JsArray<JsSticker>) JsArray.createArray());
              messenger.getAvailableStickersVM().getOwnStickerPacks().subscribe(new ValueChangedListener<ArrayList<StickerPack>>() {
                  @Override
                  public void onChanged(ArrayList<StickerPack> val, Value<ArrayList<StickerPack>> valueModel) {
                      JsArray<JsSticker> stickerJsArray = JsArray.createArray().cast();
                      stickerJsArray.setLength(0);
                      for (StickerPack pack : val) {
                          for (Sticker sticker : pack.getStickers()) {
                              stickerJsArray.push(JsSticker.create(sticker));
                          }
                      }
                      stickers.changeValue(stickerJsArray);
                  }
              });
          }
          return stickers;
      }
  
      public JsBindedValue<String> getOnlineStatus() {
          if (onlineState == null) {
  
              final GlobalStateVM vm = context().getConductor().getGlobalStateVM();
              onlineState = new JsBindedValue<>("online");
  
              vm.getIsConnecting().subscribe(new ValueChangedListener<Boolean>() {
                  @Override
                  public void onChanged(Boolean val, Value<Boolean> valueModel) {
                      if (val) {
                          onlineState.changeValue("connecting");
                      } else {
                          if (vm.getIsSyncing().get()) {
                              onlineState.changeValue("updating");
                          } else {
                              onlineState.changeValue("online");
                          }
                      }
                  }
              });
              vm.getIsSyncing().subscribe(new ValueChangedListener<Boolean>() {
                  @Override
                  public void onChanged(Boolean val, Value<Boolean> valueModel) {
                      if (vm.getIsConnecting().get()) {
                          onlineState.changeValue("connecting");
                      } else {
                          if (val) {
                              onlineState.changeValue("updating");
                          } else {
                              onlineState.changeValue("online");
                          }
                      }
                  }
              });
          }
  
          return onlineState;
      }
  
      public JsBindedValue<JsUser> getUser(int uid) {
          if (!users.containsKey(uid)) {
              final UserVM userVM = context().getUsersModule().getUsers().get(uid);
              final JsBindedValue<JsUser> value = new JsBindedValue<>(JsUser.fromUserVM(userVM, messenger));
  
              // Bind updates
              userVM.subscribe(new ModelChangedListener<UserVM>() {
                  @Override
                  public void onChanged(UserVM model) {
                      value.changeValue(JsUser.fromUserVM(userVM, messenger));
                  }
              }, false);
  
              // Sign for contact separately
              userVM.isContact().subscribe(new ValueChangedListener<Boolean>() {
                  @Override
                  public void onChanged(Boolean val, Value<Boolean> valueModel) {
                      value.changeValue(JsUser.fromUserVM(userVM, messenger));
                  }
              });
  
              // Sign for blocked separately
              userVM.getIsBlocked().subscribe(new ValueChangedListener<Boolean>() {
                  @Override
                  public void onChanged(Boolean val, Value<Boolean> valueModel) {
                      value.changeValue(JsUser.fromUserVM(userVM, messenger));
                  }
              });
  
              users.put(uid, value);
          }
          return users.get(uid);
      }
  
      public JsBindedValue<JsOnlineUser> getUserOnline(int uid) {
          if (!usersOnlines.containsKey(uid)) {
              final JsBindedValue<JsOnlineUser> value = new JsBindedValue<>();
              final UserVM userVM = context().getUsersModule().getUsers().get(uid);
  
              userVM.getPresence().subscribe(new ValueChangedListener<UserPresence>() {
                  @Override
                  public void onChanged(UserPresence val, Value<UserPresence> valueModel) {
                      if (val.getState() == UserPresence.State.UNKNOWN) {
                          value.changeValue(null);
                      } else {
                          String presenceString = messenger.getFormatter().formatPresence(val, userVM.getSex());
                          if (userVM.isBot()) {
                              presenceString = "bot";
                          }
                          value.changeValue(JsOnlineUser.create(presenceString, val.getState() == UserPresence.State.ONLINE));
                      }
                  }
              });
  
              usersOnlines.put(uid, value);
          }
          return usersOnlines.get(uid);
      }
  
      public JsBindedValue<JsBlockedUser> getUserBlocked(int uid) {
          if (!usersBloked.containsKey(uid)) {
              final JsBindedValue<JsBlockedUser> value = new JsBindedValue<>();
              final UserVM userVM = context().getUsersModule().getUsers().get(uid);
  
              userVM.getIsBlocked().subscribe(new ValueChangedListener<Boolean>() {
                  @Override
                  public void onChanged(Boolean val, Value<Boolean> valueModel) {
                      value.changeValue(JsBlockedUser.create(val));
                  }
              });
  
              usersBloked.put(uid, value);
          }
          return usersBloked.get(uid);
      }
  
      public JsBindedValue<JsGroup> getGroup(int gid) {
          if (!groups.containsKey(gid)) {
              final GroupVM groupVM = context().getGroupsModule().getGroupsCollection().get(gid);
              final JsBindedValue<JsGroup> value = new JsBindedValue<>(JsGroup.fromGroupVM(groupVM, messenger));
  
              // Bind updates
              groupVM.subscribe(new ModelChangedListener<GroupVM>() {
                  @Override
                  public void onChanged(GroupVM model) {
                      value.changeValue(JsGroup.fromGroupVM(groupVM, messenger));
                  }
              }, false);
  
              groups.put(gid, value);
          }
          return groups.get(gid);
      }
  
      public JsBindedValue<JsOnlineGroup> getGroupOnline(int gid) {
          if (!groupOnlines.containsKey(gid)) {
              final JsBindedValue<JsOnlineGroup> value = new JsBindedValue<>();
              final GroupVM groupVM = context().getGroupsModule().getGroupsCollection().get(gid);
              groupVM.getPresence().subscribe(new ValueChangedListener<Integer>() {
                  @Override
                  public void onChanged(Integer val, Value<Integer> valueModel) {
                      if (groupVM.isMember().get()) {
                          if (val == null) {
                              value.changeValue(null);
                              return;
                          }
                          String presence = messenger.getFormatter().formatGroupMembers(groupVM.getMembersCount().get());
                          if (val > 0) {
                              presence += ", " + messenger.getFormatter().formatGroupOnline(val);
                          }
                          value.changeValue(JsOnlineGroup.create(groupVM.getMembersCount().get(), val, presence, false));
                      } else {
                          value.changeValue(JsOnlineGroup.create(0, 0, "Not member", false));
                      }
                  }
              });
              groupOnlines.put(gid, value);
          }
          return groupOnlines.get(gid);
      }
  
      public JsBindedValue<JsCall> getCall(String id) {
          if (!calls.containsKey(id)) {
              final CallVM callModel = messenger.getCall(Long.parseLong(id));
              final JsBindedValue<JsCall> jsCall = new JsBindedValue<>(JsCall.create(messenger, callModel));
              callModel.getState().subscribe(new ValueChangedListener<CallState>() {
                  @Override
                  public void onChanged(CallState val, Value<CallState> valueModel) {
                      jsCall.changeValue(JsCall.create(messenger, callModel));
                  }
              });
              callModel.getMembers().subscribe(new ValueChangedListener<ArrayList<CallMember>>() {
                  @Override
                  public void onChanged(ArrayList<CallMember> val, Value<ArrayList<CallMember>> valueModel) {
                      jsCall.changeValue(JsCall.create(messenger, callModel));
                  }
              });
              callModel.getOwnVideoTracks().subscribe(new ValueChangedListener<ArrayListMediaTrack>() {
                  @Override
                  public void onChanged(ArrayListMediaTrack val, Value<ArrayListMediaTrack> valueModel) {
                      jsCall.changeValue(JsCall.create(messenger, callModel));
                  }
              });
              callModel.getTheirVideoTracks().subscribe(new ValueChangedListener<ArrayListMediaTrack>() {
                  @Override
                  public void onChanged(ArrayListMediaTrack val, Value<ArrayListMediaTrack> valueModel) {
                      jsCall.changeValue(JsCall.create(messenger, callModel));
                  }
              });
              calls.put(id, jsCall);
          }
          return calls.get(id);
      }
  
      public JsBindedValue<JsTyping> getTyping(final Peer peer) {
          if (!typing.containsKey(peer)) {
              if (peer.getPeerType() == PeerType.PRIVATE) {
                  UserTypingVM userTypingVM = context().getTypingModule().getTyping(peer.getPeerId());
  
                  final JsBindedValue<JsTyping> value = new JsBindedValue<>();
                  userTypingVM.getTyping().subscribe((val, valueModel) -> {
                      String typingValue = null;
                      if (val) {
                          typingValue = messenger.getFormatter().formatTyping("");
                      }
                      value.changeValue(JsTyping.create(typingValue));
                  });
                  typing.put(peer, value);
              } else if (peer.getPeerType() == PeerType.GROUP) {
                  GroupTypingVM groupTypingVM = context().getTypingModule().getGroupTyping(peer.getPeerId());
                  final JsBindedValue<JsTyping> value = new JsBindedValue<>();
                  groupTypingVM.getActive().subscribe((val, valueModel) -> {
                      if (val.length > 0) {
                          ArrayList<String> names = new ArrayList<>();
                          for (int i : val) {
                              names.add(context()
                                      .getUsersModule()
                                      .getUsers()
                                      .get(i)
                                      .getName()
                                      .get());
                          }
                          value.changeValue(JsTyping.create(messenger.getFormatter().formatTyping(names)));
                      } else {
                          value.changeValue(JsTyping.create(null));
                      }
                  });
                  typing.put(peer, value);
              } else {
                  throw new RuntimeException();
              }
          }
          return typing.get(peer);
      }
  
      public JsDisplayList<JsSearchEntity, SearchEntity> getSharedSearchList() {
          if (searchList == null) {
              searchList = (JsDisplayList<JsSearchEntity, SearchEntity>) context().getDisplayListsModule().buildSearchList(true);
          }
          return searchList;
      }
  
      public JsDisplayList<JsContact, Contact> getSharedContactList() {
          if (contactsList == null) {
              contactsList = (JsDisplayList<JsContact, Contact>) context().getDisplayListsModule().getContactsSharedList();
          }
  
          return contactsList;
      }
  
      public JsDisplayList<JsDialog, Dialog> getSharedDialogList() {
          if (dialogsList == null) {
              dialogsList = (JsDisplayList<JsDialog, Dialog>) context().getDisplayListsModule().getDialogsSharedList();
          }
  
          return dialogsList;
      }
  
      public JsDisplayList<JsMessage, Message> getSharedMessageList(Peer peer) {
          if (!messageLists.containsKey(peer)) {
              messageLists.put(peer,
                      (JsDisplayList<JsMessage, Message>) context().getDisplayListsModule().getMessagesSharedList(peer));
          }
  
          return messageLists.get(peer);
      }
  
      public JsBindedValue<JsCounter> getGlobalCounter() {
          if (globalCounter == null) {
              ValueModel<Integer> counter = context().getConductor().getGlobalStateVM().getGlobalCounter();
              globalCounter = new JsBindedValue<>(JsCounter.create(counter.get()));
              counter.subscribe(new ValueChangedListener<Integer>() {
                  @Override
                  public void onChanged(Integer val, Value<Integer> valueModel) {
                      globalCounter.changeValue(JsCounter.create(val));
                  }
              }, false);
          }
          return globalCounter;
      }
  
      public JsBindedValue<JsCounter> getTempGlobalCounter() {
          if (tempGlobalCounter == null) {
              ValueModel<Integer> counter = context().getConductor().getGlobalStateVM().getGlobalTempCounter();
              tempGlobalCounter = new JsBindedValue<>(JsCounter.create(counter.get()));
              counter.subscribe(new ValueChangedListener<Integer>() {
                  @Override
                  public void onChanged(Integer val, Value<Integer> valueModel) {
                      tempGlobalCounter.changeValue(JsCounter.create(val));
                  }
              }, false);
          }
          return tempGlobalCounter;
      }
  
      @Override
      public void onFileLoaded(HashSet<Long> fileId) {
  
          //
          // Dialogs List
          //
  
          if (dialogsList != null) {
              for (JsDisplayListBind<JsDialog, Dialog> b : dialogsList.getActiveBinds()) {
                  b.startReconverting();
                  for (Dialog dialog : b.getRawItems()) {
                      if (checkAvatar(dialog.getDialogAvatar(), fileId)) {
                          b.forceReconvert(dialog.getEngineId());
                      }
                  }
                  b.stopReconverting();
              }
          }
  
          //
          // Grouped Dialogs
          //
  
          if (dialogsGroupedList != null) {
              ArrayList<DialogGroup> groups = context().getMessagesModule().getDialogGroupsVM().getGroupsValueModel().get();
              if (groups != null) {
                  outer:
                  for (DialogGroup g : groups) {
                      for (DialogSmall ds : g.getDialogs()) {
                          if (checkAvatar(ds.getAvatar(), fileId)) {
                              context().getMessagesModule().getDialogGroupsVM().getGroupsValueModel().forceNotify();
                              break outer;
                          }
                      }
                  }
              }
          }
  
          //
          // Contacts List
          //
  
          if (contactsList != null) {
              for (JsDisplayListBind<JsContact, Contact> b : contactsList.getActiveBinds()) {
                  b.startReconverting();
                  for (Contact contact : b.getRawItems()) {
                      if (checkAvatar(contact.getAvatar(), fileId)) {
                          b.forceReconvert(contact.getEngineId());
                      }
                  }
                  b.stopReconverting();
              }
          }
  
          //
          // Message Contents
          //
  
          for (JsDisplayList<JsMessage, Message> messageList : messageLists.values()) {
              for (JsDisplayListBind<JsMessage, Message> b : messageList.getActiveBinds()) {
                  b.startReconverting();
                  for (Message message : b.getRawItems()) {
                      UserVM user = context().getUsersModule().getUsers().get(message.getSenderId());
                      if (checkAvatar(user.getAvatar().get(), fileId)) {
                          b.forceReconvert(message.getEngineId());
                          continue;
                      }
                      if (message.getContent() instanceof DocumentContent) {
                          DocumentContent doc = (DocumentContent) message.getContent();
                          if (doc.getSource() instanceof FileRemoteSource) {
                              if (fileId.contains(((FileRemoteSource) doc.getSource()).getFileReference().getFileId())) {
                                  b.forceReconvert(message.getEngineId());
                              }
                          }
                      }
                      if (message.getContent() instanceof StickerContent) {
                          StickerContent content = (StickerContent) message.getContent();
                          if (content.getImage512() != null) {
                              long stickerFileId = content.getImage512().getReference().getFileId();
                              if (fileId.contains(stickerFileId)) {
                                  b.forceReconvert(message.getEngineId());
                              }
                          } else if (content.getImage256() != null) {
                              long stickerFileId = content.getImage256().getReference().getFileId();
                              if (fileId.contains(stickerFileId)) {
                                  b.forceReconvert(message.getEngineId());
                              }
                          }
                      }
                  }
                  b.stopReconverting();
              }
          }
  
          //
          // Users
          //
  
          for (JsBindedValue<JsUser> u : users.values()) {
              int uid = u.get().getUid();
              UserVM userVM = context().getUsersModule().getUsers().get(uid);
              if (checkAvatar(userVM.getAvatar().get(), fileId)) {
                  u.changeValue(JsUser.fromUserVM(userVM, messenger));
              }
          }
  
          //
          // Groups
          //
  
          for (JsBindedValue<JsGroup> g : groups.values()) {
              int gid = g.get().getGid();
              GroupVM groupVM = context().getGroupsModule().getGroupsCollection().get(gid);
              if (checkAvatar(groupVM.getAvatar().get(), fileId)) {
                  g.changeValue(JsGroup.fromGroupVM(groupVM, messenger));
              }
          }
  
          //
          // Stickers
          //
          if (stickers != null) {
              outer:
              for (StickerPack stickerPack : messenger.getAvailableStickersVM().getOwnStickerPacks().get()) {
                  for (Sticker s : stickerPack.getStickers()) {
                      if (s.getImage256() != null && fileId.contains(s.getImage256().getFileId())) {
                          messenger.getAvailableStickersVM().getOwnStickerPacks().forceNotify();
                          break outer;
                      }
                  }
              }
          }
      }
  
      protected boolean checkAvatar(Avatar avatar, HashSet<Long> fileIds) {
          if (avatar == null) {
              return false;
          }
          if (avatar.getSmallImage() != null && fileIds.contains(avatar.getSmallImage().getFileReference().getFileId())) {
              return true;
          }
          if (avatar.getFullImage() != null && fileIds.contains(avatar.getFullImage().getFileReference().getFileId())) {
              return true;
          }
          if (avatar.getLargeImage() != null && fileIds.contains(avatar.getLargeImage().getFileReference().getFileId())) {
              return true;
          }
          return false;
      }
  }
  