content: /*
   * Copyright (C) 2015 Actor LLC. <https://actor.im>
   */
  
  package im.actor.core.modules.contacts;
  
  import java.util.ArrayList;
  
  import im.actor.core.api.ApiOutPeer;
  import im.actor.core.api.ApiUser;
  import im.actor.core.api.ApiUserOutPeer;
  import im.actor.core.api.base.SeqUpdate;
  import im.actor.core.api.rpc.RequestAddContact;
  import im.actor.core.api.rpc.RequestRemoveContact;
  import im.actor.core.api.rpc.RequestSearchContacts;
  import im.actor.core.api.rpc.ResponseSeq;
  import im.actor.core.api.updates.UpdateContactsAdded;
  import im.actor.core.api.updates.UpdateContactsRemoved;
  import im.actor.core.entity.User;
  import im.actor.core.modules.AbsModule;
  import im.actor.core.modules.api.ApiSupportConfiguration;
  import im.actor.core.modules.Modules;
  import im.actor.core.viewmodel.Command;
  import im.actor.runtime.Storage;
  import im.actor.runtime.actors.ActorRef;
  import im.actor.runtime.actors.Props;
  import im.actor.runtime.promise.Promise;
  import im.actor.runtime.storage.ListEngine;
  import im.actor.core.entity.Contact;
  import im.actor.core.network.RpcCallback;
  import im.actor.core.network.RpcException;
  import im.actor.core.network.RpcInternalException;
  import im.actor.core.viewmodel.UserVM;
  import im.actor.runtime.storage.SyncKeyValue;
  
  import static im.actor.runtime.actors.ActorSystem.system;
  
  public class ContactsModule extends AbsModule {
  
      private ListEngine<Contact> contacts;
      private ActorRef bookImportActor;
      private ActorRef contactSyncActor;
      private SyncKeyValue bookImportState;
  
      public ContactsModule(final Modules modules) {
          super(modules);
  
          contacts = Storage.createList(STORAGE_CONTACTS, Contact.CREATOR);
          bookImportState = new SyncKeyValue(Storage.createKeyValue(STORAGE_BOOK_IMPORT));
      }
  
      public void run() {
          bookImportActor = system().actorOf(Props.create(() -> new BookImportActor(context())).changeDispatcher("heavy"), "actor/book_import");
          contactSyncActor = system().actorOf(Props.create(() -> new ContactsSyncActor(context())).changeDispatcher("heavy"), "actor/contacts_sync");
      }
  
      public SyncKeyValue getBookImportState() {
          return bookImportState;
      }
  
      public void startImport() {
          bookImportActor.send(new BookImportActor.Start());
      }
  
      public ListEngine<Contact> getContacts() {
          return contacts;
      }
  
      public void onPhoneBookChanged() {
          bookImportActor.send(new BookImportActor.PerformSync());
      }
  
      public ActorRef getContactSyncActor() {
          return contactSyncActor;
      }
  
      public void markContact(int uid) {
          preferences().putBool("contact_" + uid, true);
      }
  
      public void markNonContact(int uid) {
          preferences().putBool("contact_" + uid, false);
      }
  
      public boolean isUserContact(int uid) {
          return preferences().getBool("contact_" + uid, false);
      }
  
      public void markInPhoneBook(int uid) {
          preferences().putBool("contact_in_pb_" + uid, true);
      }
  
      public void markNotInPhoneBook(int uid) {
          preferences().putBool("contact_in_pb_" + uid, false);
      }
  
      public boolean isUserInPhoneBook(int uid) {
          return preferences().getBool("contact_in_pb_" + uid, false);
      }
  
      public Promise<UserVM[]> findUsers(final String query) {
          return api(new RequestSearchContacts(query, ApiSupportConfiguration.OPTIMIZATIONS))
                  .chain(responseSearchContacts -> updates().loadRequiredPeers(responseSearchContacts.getUserPeers(), new ArrayList<>()))
                  .map(responseSearchContacts1 -> {
                      ArrayList<UserVM> users = new ArrayList<>();
                      for (ApiUserOutPeer u : responseSearchContacts1.getUserPeers()) {
                          users.add(context().getUsersModule().getUsers().get(u.getUid()));
                      }
                      return users.toArray(new UserVM[users.size()]);
                  });
      }
  
      public Command<Boolean> addContact(final int uid) {
          return callback -> {
              User user = users().getValue(uid);
              if (user == null) {
                  runOnUiThread(() -> callback.onError(new RpcInternalException()));
                  return;
              }
  
              request(new RequestAddContact(uid, user.getAccessHash()), new RpcCallback<ResponseSeq>() {
                  @Override
                  public void onResult(ResponseSeq response) {
                      ArrayList<Integer> uids = new ArrayList<>();
                      uids.add(uid);
                      SeqUpdate update = new SeqUpdate(response.getSeq(), response.getState(),
                              UpdateContactsAdded.HEADER, new UpdateContactsAdded(uids).toByteArray());
                      updates().onUpdateReceived(update);
                      runOnUiThread(() -> callback.onResult(true));
                  }
  
                  @Override
                  public void onError(RpcException e) {
                      runOnUiThread(() -> callback.onError(new RpcInternalException()));
                  }
              });
          };
      }
  
      public Command<Boolean> removeContact(final int uid) {
          return callback -> {
              User user = users().getValue(uid);
              if (user == null) {
                  runOnUiThread(() -> callback.onError(new RpcInternalException()));
                  return;
              }
  
              request(new RequestRemoveContact(uid, user.getAccessHash()), new RpcCallback<ResponseSeq>() {
                  @Override
                  public void onResult(ResponseSeq response) {
                      ArrayList<Integer> uids = new ArrayList<>();
                      uids.add(uid);
                      SeqUpdate update = new SeqUpdate(response.getSeq(), response.getState(),
                              UpdateContactsRemoved.HEADER, new UpdateContactsRemoved(uids).toByteArray());
                      updates().onUpdateReceived(update);
                      runOnUiThread(() -> callback.onResult(true));
                  }
  
                  @Override
                  public void onError(RpcException e) {
                      runOnUiThread(() -> callback.onError(new RpcInternalException()));
                  }
              });
          };
      }
  
      public void resetModule() {
          // TODO: Implement
      }
  }
  