content: /*
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.internal.widget;
  
  import static com.android.internal.widget.AdapterHelper.UpdateOp.ADD;
  import static com.android.internal.widget.AdapterHelper.UpdateOp.MOVE;
  import static com.android.internal.widget.AdapterHelper.UpdateOp.REMOVE;
  import static com.android.internal.widget.AdapterHelper.UpdateOp.UPDATE;
  
  import com.android.internal.widget.AdapterHelper.UpdateOp;
  
  import java.util.List;
  
  class OpReorderer {
  
      final Callback mCallback;
  
      OpReorderer(Callback callback) {
          mCallback = callback;
      }
  
      void reorderOps(List<UpdateOp> ops) {
          // since move operations breaks continuity, their effects on ADD/RM are hard to handle.
          // we push them to the end of the list so that they can be handled easily.
          int badMove;
          while ((badMove = getLastMoveOutOfOrder(ops)) != -1) {
              swapMoveOp(ops, badMove, badMove + 1);
          }
      }
  
      private void swapMoveOp(List<UpdateOp> list, int badMove, int next) {
          final UpdateOp moveOp = list.get(badMove);
          final UpdateOp nextOp = list.get(next);
          switch (nextOp.cmd) {
              case REMOVE:
                  swapMoveRemove(list, badMove, moveOp, next, nextOp);
                  break;
              case ADD:
                  swapMoveAdd(list, badMove, moveOp, next, nextOp);
                  break;
              case UPDATE:
                  swapMoveUpdate(list, badMove, moveOp, next, nextOp);
                  break;
          }
      }
  
      void swapMoveRemove(List<UpdateOp> list, int movePos, UpdateOp moveOp,
              int removePos, UpdateOp removeOp) {
          UpdateOp extraRm = null;
          // check if move is nulled out by remove
          boolean revertedMove = false;
          final boolean moveIsBackwards;
  
          if (moveOp.positionStart < moveOp.itemCount) {
              moveIsBackwards = false;
              if (removeOp.positionStart == moveOp.positionStart
                      && removeOp.itemCount == moveOp.itemCount - moveOp.positionStart) {
                  revertedMove = true;
              }
          } else {
              moveIsBackwards = true;
              if (removeOp.positionStart == moveOp.itemCount + 1
                      && removeOp.itemCount == moveOp.positionStart - moveOp.itemCount) {
                  revertedMove = true;
              }
          }
  
          // going in reverse, first revert the effect of add
          if (moveOp.itemCount < removeOp.positionStart) {
              removeOp.positionStart--;
          } else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) {
              // move is removed.
              removeOp.itemCount--;
              moveOp.cmd = REMOVE;
              moveOp.itemCount = 1;
              if (removeOp.itemCount == 0) {
                  list.remove(removePos);
                  mCallback.recycleUpdateOp(removeOp);
              }
              // no need to swap, it is already a remove
              return;
          }
  
          // now affect of add is consumed. now apply effect of first remove
          if (moveOp.positionStart <= removeOp.positionStart) {
              removeOp.positionStart++;
          } else if (moveOp.positionStart < removeOp.positionStart + removeOp.itemCount) {
              final int remaining = removeOp.positionStart + removeOp.itemCount
                      - moveOp.positionStart;
              extraRm = mCallback.obtainUpdateOp(REMOVE, moveOp.positionStart + 1, remaining, null);
              removeOp.itemCount = moveOp.positionStart - removeOp.positionStart;
          }
  
          // if effects of move is reverted by remove, we are done.
          if (revertedMove) {
              list.set(movePos, removeOp);
              list.remove(removePos);
              mCallback.recycleUpdateOp(moveOp);
              return;
          }
  
          // now find out the new locations for move actions
          if (moveIsBackwards) {
              if (extraRm != null) {
                  if (moveOp.positionStart > extraRm.positionStart) {
                      moveOp.positionStart -= extraRm.itemCount;
                  }
                  if (moveOp.itemCount > extraRm.positionStart) {
                      moveOp.itemCount -= extraRm.itemCount;
                  }
              }
              if (moveOp.positionStart > removeOp.positionStart) {
                  moveOp.positionStart -= removeOp.itemCount;
              }
              if (moveOp.itemCount > removeOp.positionStart) {
                  moveOp.itemCount -= removeOp.itemCount;
              }
          } else {
              if (extraRm != null) {
                  if (moveOp.positionStart >= extraRm.positionStart) {
                      moveOp.positionStart -= extraRm.itemCount;
                  }
                  if (moveOp.itemCount >= extraRm.positionStart) {
                      moveOp.itemCount -= extraRm.itemCount;
                  }
              }
              if (moveOp.positionStart >= removeOp.positionStart) {
                  moveOp.positionStart -= removeOp.itemCount;
              }
              if (moveOp.itemCount >= removeOp.positionStart) {
                  moveOp.itemCount -= removeOp.itemCount;
              }
          }
  
          list.set(movePos, removeOp);
          if (moveOp.positionStart != moveOp.itemCount) {
              list.set(removePos, moveOp);
          } else {
              list.remove(removePos);
          }
          if (extraRm != null) {
              list.add(movePos, extraRm);
          }
      }
  
      private void swapMoveAdd(List<UpdateOp> list, int move, UpdateOp moveOp, int add,
              UpdateOp addOp) {
          int offset = 0;
          // going in reverse, first revert the effect of add
          if (moveOp.itemCount < addOp.positionStart) {
              offset--;
          }
          if (moveOp.positionStart < addOp.positionStart) {
              offset++;
          }
          if (addOp.positionStart <= moveOp.positionStart) {
              moveOp.positionStart += addOp.itemCount;
          }
          if (addOp.positionStart <= moveOp.itemCount) {
              moveOp.itemCount += addOp.itemCount;
          }
          addOp.positionStart += offset;
          list.set(move, addOp);
          list.set(add, moveOp);
      }
  
      void swapMoveUpdate(List<UpdateOp> list, int move, UpdateOp moveOp, int update,
              UpdateOp updateOp) {
          UpdateOp extraUp1 = null;
          UpdateOp extraUp2 = null;
          // going in reverse, first revert the effect of add
          if (moveOp.itemCount < updateOp.positionStart) {
              updateOp.positionStart--;
          } else if (moveOp.itemCount < updateOp.positionStart + updateOp.itemCount) {
              // moved item is updated. add an update for it
              updateOp.itemCount--;
              extraUp1 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart, 1, updateOp.payload);
          }
          // now affect of add is consumed. now apply effect of first remove
          if (moveOp.positionStart <= updateOp.positionStart) {
              updateOp.positionStart++;
          } else if (moveOp.positionStart < updateOp.positionStart + updateOp.itemCount) {
              final int remaining = updateOp.positionStart + updateOp.itemCount
                      - moveOp.positionStart;
              extraUp2 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart + 1, remaining,
                      updateOp.payload);
              updateOp.itemCount -= remaining;
          }
          list.set(update, moveOp);
          if (updateOp.itemCount > 0) {
              list.set(move, updateOp);
          } else {
              list.remove(move);
              mCallback.recycleUpdateOp(updateOp);
          }
          if (extraUp1 != null) {
              list.add(move, extraUp1);
          }
          if (extraUp2 != null) {
              list.add(move, extraUp2);
          }
      }
  
      private int getLastMoveOutOfOrder(List<UpdateOp> list) {
          boolean foundNonMove = false;
          for (int i = list.size() - 1; i >= 0; i--) {
              final UpdateOp op1 = list.get(i);
              if (op1.cmd == MOVE) {
                  if (foundNonMove) {
                      return i;
                  }
              } else {
                  foundNonMove = true;
              }
          }
          return -1;
      }
  
      interface Callback {
  
          UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount, Object payload);
  
          void recycleUpdateOp(UpdateOp op);
      }
  }
  