content: /*
   * Copyright (C) 2015 Actor LLC. <https://actor.im>
   */
  
  package im.actor.runtime.generic.mvvm.alg;
  
  import com.google.j2objc.annotations.AutoreleasePool;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import im.actor.runtime.generic.mvvm.ChangeDescription;
  import im.actor.runtime.storage.ListEngineItem;
  
  // Disabling Bounds checks for speeding up calculations
  
  /*-[
  #define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1
  ]-*/
  
  public class Modifications {
  
      public static <T extends ListEngineItem> Modification<T> noOp() {
          return new Modification<T>() {
              @Override
              public List<ChangeDescription<T>> modify(ArrayList<T> sourceList) {
                  return new ArrayList<>();
              }
          };
      }
  
      public static <T extends ListEngineItem> Modification<T> addOrUpdate(final T item) {
          ArrayList<T> res = new ArrayList<T>();
          res.add(item);
          return addOrUpdate(res);
      }
  
      public static <T extends ListEngineItem> Modification<T> addOrUpdate(final List<T> items) {
          return new Modification<T>() {
              @Override
              public List<ChangeDescription<T>> modify(ArrayList<T> sourceList) {
                  ArrayList<ChangeDescription<T>> res = new ArrayList<>();
                  addOrUpdate(items, sourceList, res, false);
                  return res;
              }
          };
      }
  
      public static <T extends ListEngineItem> Modification<T> addLoadMore(final List<T> items) {
          return new Modification<T>() {
              @Override
              public List<ChangeDescription<T>> modify(ArrayList<T> sourceList) {
                  ArrayList<ChangeDescription<T>> res = new ArrayList<>();
                  addOrUpdate(items, sourceList, res, true);
                  return res;
              }
          };
      }
  
      public static <T extends ListEngineItem> Modification<T> replace(final List<T> items) {
          return new Modification<T>() {
              @Override
              public List<ChangeDescription<T>> modify(ArrayList<T> sourceList) {
                  ArrayList<ChangeDescription<T>> res = new ArrayList<>();
                  replace(items, sourceList, res);
                  return res;
              }
          };
      }
  
      public static <T extends ListEngineItem> Modification<T> remove(final long dstId) {
          return remove(new long[]{dstId});
      }
  
      public static <T extends ListEngineItem> Modification<T> remove(final long[] dstIds) {
          return new Modification<T>() {
              @Override
              public List<ChangeDescription<T>> modify(ArrayList<T> sourceList) {
                  ArrayList<ChangeDescription<T>> res = new ArrayList<>();
                  for (int i = 0; i < sourceList.size(); i++) {
                      ListEngineItem src = sourceList.get(i);
                      for (long aDstId : dstIds) {
                          if (src.getEngineId() == aDstId) {
                              sourceList.remove(i);
                              res.add(ChangeDescription.<T>remove(i));
                              i--;
                              break;
                          }
                      }
                  }
                  return res;
              }
          };
      }
  
      public static <T> Modification<T> clear() {
          return new Modification<T>() {
              @Override
              public List<ChangeDescription<T>> modify(ArrayList<T> sourceList) {
                  ArrayList<ChangeDescription<T>> res = new ArrayList<>();
                  if (sourceList.size() != 0) {
                      res.add(ChangeDescription.<T>remove(0, sourceList.size()));
                      sourceList.clear();
                  }
                  return res;
              }
          };
      }
  
      @AutoreleasePool
      private static <T extends ListEngineItem> void replace(List<T> items,
                                                             ArrayList<T> sourceList,
                                                             ArrayList<ChangeDescription<T>> changes) {
          // Remove missing items
          outer:
          for (int i = 0; i < sourceList.size(); i++) {
              long id = sourceList.get(i).getEngineId();
  
              for (T itm : items) {
                  if (itm.getEngineId() == id) {
                      continue outer;
                  }
              }
  
              changes.add(ChangeDescription.<T>remove(i));
              sourceList.remove(i);
              i--;
          }
  
          for (T itm : items) {
              addOrUpdate(itm, sourceList, changes, false);
          }
      }
  
      @AutoreleasePool
      private static <T extends ListEngineItem> void addOrUpdate(List<T> items,
                                                                 ArrayList<T> sourceList,
                                                                 ArrayList<ChangeDescription<T>> changes,
                                                                 boolean isLoadMore) {
          for (T toAdd : items) {
              addOrUpdate(toAdd, sourceList, changes, isLoadMore);
          }
      }
  
      @AutoreleasePool
      private static <T extends ListEngineItem> void addOrUpdate(T item,
                                                                 ArrayList<T> sourceList,
                                                                 ArrayList<ChangeDescription<T>> changes,
                                                                 boolean isLoadMore) {
          long id = item.getEngineId();
          long sortKey = item.getEngineSort();
  
          // Finding suitable place for item
          int removedIndex = -1;
          int addedIndex = -1;
          for (int i = 0; i < sourceList.size(); i++) {
              T srcItem = sourceList.get(i);
              if (srcItem.getEngineId() == id) {
                  if (isLoadMore) {
                      return;
                  }
                  // Remove old item
                  sourceList.remove(i);
                  if (addedIndex >= 0) {
                      removedIndex = i - 1;
                  } else {
                      removedIndex = i;
                  }
                  i--;
                  continue;
              } else {
                  // TODO: Fix ADD ONLY
                  if ((addedIndex < 0) && sortKey > srcItem.getEngineSort()) {
                      addedIndex = i;
                      sourceList.add(i, item);
                      i++;
                  }
              }
  
              // Already founded
              if (addedIndex >= 0 && removedIndex >= 0) {
                  break;
              }
          }
  
          // If no place for insert: insert to end
          if (addedIndex < 0) {
              addedIndex = sourceList.size();
              sourceList.add(sourceList.size(), item);
          }
  
          if (addedIndex == removedIndex) {
              // If there are no movement: just update item in place
              changes.add(ChangeDescription.update(addedIndex, item));
          } else if (removedIndex >= 0) {
              // Movement + update occurred
              changes.add(ChangeDescription.update(removedIndex, item));
              changes.add(ChangeDescription.<T>move(removedIndex, addedIndex));
          } else {
              // No old element found: add new element
              changes.add(ChangeDescription.add(addedIndex, item));
          }
      }
  
  }
  