content: /*
   * Copyright (C) 2015 Actor LLC. <https://actor.im>
   */
  
  package im.actor.core.modules.contacts;
  
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.List;
  
  import im.actor.core.api.ApiUser;
  import im.actor.core.api.ApiUserOutPeer;
  import im.actor.core.api.rpc.RequestGetContacts;
  import im.actor.core.api.rpc.ResponseGetContacts;
  import im.actor.core.entity.Contact;
  import im.actor.core.entity.User;
  import im.actor.core.modules.api.ApiSupportConfiguration;
  import im.actor.core.modules.ModuleContext;
  import im.actor.core.modules.ModuleActor;
  import im.actor.core.network.RpcCallback;
  import im.actor.core.network.RpcException;
  import im.actor.runtime.Crypto;
  import im.actor.runtime.Log;
  import im.actor.runtime.actors.messages.Void;
  import im.actor.runtime.bser.DataInput;
  import im.actor.runtime.bser.DataOutput;
  
  public class ContactsSyncActor extends ModuleActor {
  
      private static final String TAG = "ContactsServerSync";
  
      // j2objc workaround
      private static final Void DUMB = null;
  
      private final boolean ENABLE_LOG;
  
      private ArrayList<Integer> contacts = new ArrayList<>();
  
      private boolean isInProgress = false;
      private boolean isInvalidated = false;
  
      public ContactsSyncActor(ModuleContext context) {
          super(context);
          ENABLE_LOG = context.getConfiguration().isEnableContactsLogging();
      }
  
      @Override
      public void preStart() {
          super.preStart();
          if (ENABLE_LOG) {
              Log.d(TAG, "Loading contacts ids from storage...");
          }
          byte[] data = preferences().getBytes("contact_list");
          if (data != null) {
              try {
                  DataInput dataInput = new DataInput(data, 0, data.length);
                  int count = dataInput.readInt();
                  for (int i = 0; i < count; i++) {
                      int uid = dataInput.readInt();
                      if (!contacts.contains(uid)) {
                          contacts.add(uid);
                      }
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
          notifyState();
          self().send(new PerformSync());
      }
  
      public void performSync() {
          if (ENABLE_LOG) {
              Log.d(TAG, "Checking sync");
          }
  
          if (isInProgress) {
              if (ENABLE_LOG) {
                  Log.d(TAG, "Sync in progress, invalidating current sync");
              }
              isInvalidated = true;
              return;
          }
          isInProgress = true;
          isInvalidated = false;
  
          if (ENABLE_LOG) {
              Log.d(TAG, "Starting sync");
          }
  
          Integer[] uids = contacts.toArray(new Integer[contacts.size()]);
          Arrays.sort(uids);
          String hash = "";
          for (long u : uids) {
              if (hash.length() != 0) {
                  hash += ",";
              }
              hash += u;
          }
          byte[] hashData;
          try {
              hashData = hash.getBytes("UTF-8");
          } catch (UnsupportedEncodingException e) {
              e.printStackTrace();
              return;
          }
          String hashValue = Crypto.hex(Crypto.SHA256(hashData));
  
          if (ENABLE_LOG) {
              Log.d(TAG, "Performing sync with hash: " + hashValue);
              Log.d(TAG, "Performing sync with uids: " + hash);
          }
  
          request(new RequestGetContacts(hashValue, ApiSupportConfiguration.OPTIMIZATIONS), new RpcCallback<ResponseGetContacts>() {
              @Override
              public void onResult(ResponseGetContacts response) {
  
                  if (ENABLE_LOG) {
                      Log.d(TAG, "Sync received " + (response.getUsers().size() + response.getUserPeers().size()) + " contacts");
                  }
  
                  if (response.getUserPeers().size() > 0) {
                      updates().loadRequiredPeers(response.getUserPeers(), new ArrayList<>())
                              .then(v -> onContactsLoaded(response));
                  } else {
                      updates().applyRelatedData(response.getUsers())
                              .then(v -> onContactsLoaded(response));
                  }
              }
  
              @Override
              public void onError(RpcException e) {
                  isInProgress = false;
                  e.printStackTrace();
              }
          });
      }
  
      public void onContactsLoaded(ResponseGetContacts result) {
          if (ENABLE_LOG) {
              Log.d(TAG, "Sync result received");
          }
  
          isInProgress = false;
  
          context().getConductor().getConductor().onContactsLoaded();
  
          if (result.isNotChanged()) {
              Log.d(TAG, "Sync: Not changed");
              if (isInvalidated) {
                  performSync();
              } else {
                  // ProfileSyncState.onContactsLoaded(contactUsers.size() == 0);
              }
              return;
          }
  
          // Reading all uids
          HashSet<Integer> uids = new HashSet<>();
          for (ApiUser u : result.getUsers()) {
              uids.add(u.getId());
          }
          for (ApiUserOutPeer u : result.getUserPeers()) {
              uids.add(u.getUid());
          }
  
          if (ENABLE_LOG) {
              Log.d(TAG, "Sync received " + uids.size() + " contacts");
          }
  
          outer:
          for (Integer uid : contacts.toArray(new Integer[contacts.size()])) {
              for (Integer u : uids) {
                  if (u.equals(uid)) {
                      continue outer;
                  }
              }
              if (ENABLE_LOG) {
                  Log.d(TAG, "Removing: #" + uid);
              }
              contacts.remove((Integer) uid);
              if (getUser(uid) != null) {
                  getUserVM(uid).isContact().change(false);
              }
              context().getContactsModule().markNonContact(uid);
          }
          for (Integer u : uids) {
              if (contacts.contains(u)) {
                  continue;
              }
              if (ENABLE_LOG) {
                  Log.d(TAG, "Adding: #" + u);
              }
              contacts.add(u);
              if (getUser(u) != null) {
                  getUserVM(u).isContact().change(true);
              }
              context().getContactsModule().markContact(u);
          }
          saveList();
  
          updateEngineList();
  
          if (isInvalidated) {
              self().send(new PerformSync());
          }
      }
  
      public void onContactsAdded(int[] uids) {
          if (ENABLE_LOG) {
              Log.d(TAG, "OnContactsAdded received");
          }
  
          for (int uid : uids) {
              if (contacts.contains(uid)) {
                  continue;
              }
              if (ENABLE_LOG) {
                  Log.d(TAG, "Adding: #" + uid);
              }
              contacts.add(uid);
              context().getContactsModule().markContact(uid);
              getUserVM(uid).isContact().change(true);
          }
          saveList();
  
          updateEngineList();
  
          self().send(new PerformSync());
      }
  
      public void onContactsRemoved(int[] uids) {
          if (ENABLE_LOG) {
              Log.d(TAG, "OnContactsRemoved received");
          }
  
          for (int uid : uids) {
              Log.d(TAG, "Removing: #" + uid);
              contacts.remove((Integer) uid);
              context().getContactsModule().markNonContact(uid);
              getUserVM(uid).isContact().change(false);
          }
          saveList();
  
          updateEngineList();
  
          self().send(new PerformSync());
      }
  
      public void onUserChanged(User user) {
          if (ENABLE_LOG) {
              Log.d(TAG, "OnUserChanged #" + user.getUid() + " received");
          }
  
          if (!contacts.contains(user.getUid())) {
              return;
          }
  
          updateEngineList();
      }
  
      private void updateEngineList() {
          if (ENABLE_LOG) {
              Log.d(TAG, "Saving contact EngineList");
          }
          ArrayList<User> userList = new ArrayList<>();
          for (int u : contacts) {
              userList.add(getUser(u));
          }
          Collections.sort(userList, (lhs, rhs) -> lhs.getName().compareTo(rhs.getName()));
  
          List<Contact> registeredContacts = new ArrayList<>();
          int index = -1;
          for (User userModel : userList) {
              Contact contact = new Contact(userModel.getUid(),
                      (long) index--,
                      userModel.getAvatar(),
                      userModel.getName());
              registeredContacts.add(contact);
          }
          context().getContactsModule().getContacts().replaceItems(registeredContacts);
          Integer[] sorted = new Integer[contacts.size()];
          int sindex = 0;
          for (User userModel : userList) {
              sorted[sindex++] = userModel.getUid();
          }
          context().getSearchModule().onContactsChanged(sorted);
  
          notifyState();
      }
  
      private void saveList() {
          if (ENABLE_LOG) {
              Log.d(TAG, "Saving contacts ids to storage");
          }
          DataOutput dataOutput = new DataOutput();
          dataOutput.writeInt(contacts.size());
          for (int l : contacts) {
              dataOutput.writeInt(l);
          }
          preferences().putBytes("contact_list", dataOutput.toByteArray());
      }
  
      private void notifyState() {
          context().getConductor().getConductor().onContactsChanged(context().getContactsModule().getContacts().isEmpty());
      }
  
      @Override
      public void onReceive(Object message) {
          if (message instanceof ContactsAdded) {
              onContactsAdded(((ContactsAdded) message).getUids());
          } else if (message instanceof ContactsRemoved) {
              onContactsRemoved(((ContactsRemoved) message).getUids());
          } else if (message instanceof UserChanged) {
              onUserChanged(((UserChanged) message).getUser());
          } else if (message instanceof PerformSync) {
              performSync();
          } else {
              super.onReceive(message);
          }
      }
  
      private static class PerformSync {
  
      }
  
      public static class ContactsAdded {
          private int[] uids;
  
          public ContactsAdded(int[] uids) {
              this.uids = uids;
          }
  
          public int[] getUids() {
              return uids;
          }
      }
  
      public static class ContactsRemoved {
          private int[] uids;
  
          public ContactsRemoved(int[] uids) {
              this.uids = uids;
          }
  
          public int[] getUids() {
              return uids;
          }
      }
  
      public static class UserChanged {
          private User user;
  
          public UserChanged(User user) {
              this.user = user;
          }
  
          public User getUser() {
              return user;
          }
      }
  }