content: /*
   * Copyright (C) 2019 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 androidx.constraintlayout.core.widgets.analyzer;
  
  import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
  import static androidx.constraintlayout.core.widgets.ConstraintWidget.GONE;
  import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
  import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP;
  import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
  
  import androidx.constraintlayout.core.widgets.ConstraintAnchor;
  import androidx.constraintlayout.core.widgets.ConstraintWidget;
  import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
  
  import java.util.ArrayList;
  
  public class ChainRun extends WidgetRun {
      ArrayList<WidgetRun> mWidgets = new ArrayList<>();
      private int mChainStyle;
  
      public ChainRun(ConstraintWidget widget, int orientation) {
          super(widget);
          this.orientation = orientation;
          build();
      }
  
      @Override
      public String toString() {
          StringBuilder log = new StringBuilder("ChainRun ");
          log.append((orientation == HORIZONTAL ? "horizontal : " : "vertical : "));
          for (WidgetRun run : mWidgets) {
              log.append("<");
              log.append(run);
              log.append("> ");
          }
          return log.toString();
      }
  
      @Override
      boolean supportsWrapComputation() {
          final int count = mWidgets.size();
          for (int i = 0; i < count; i++) {
              WidgetRun run = mWidgets.get(i);
              if (!run.supportsWrapComputation()) {
                  return false;
              }
          }
          return true;
      }
  
      // @TODO: add description
      @Override
      public long getWrapDimension() {
          final int count = mWidgets.size();
          long wrapDimension = 0;
          for (int i = 0; i < count; i++) {
              WidgetRun run = mWidgets.get(i);
              wrapDimension += run.start.mMargin;
              wrapDimension += run.getWrapDimension();
              wrapDimension += run.end.mMargin;
          }
          return wrapDimension;
      }
  
      private void build() {
          ConstraintWidget current = mWidget;
          ConstraintWidget previous = current.getPreviousChainMember(orientation);
          while (previous != null) {
              current = previous;
              previous = current.getPreviousChainMember(orientation);
          }
          mWidget = current; // first element of the chain
          mWidgets.add(current.getRun(orientation));
          ConstraintWidget next = current.getNextChainMember(orientation);
          while (next != null) {
              current = next;
              mWidgets.add(current.getRun(orientation));
              next = current.getNextChainMember(orientation);
          }
          for (WidgetRun run : mWidgets) {
              if (orientation == HORIZONTAL) {
                  run.mWidget.horizontalChainRun = this;
              } else if (orientation == ConstraintWidget.VERTICAL) {
                  run.mWidget.verticalChainRun = this;
              }
          }
          boolean isInRtl = (orientation == HORIZONTAL)
                  && ((ConstraintWidgetContainer) mWidget.getParent()).isRtl();
          if (isInRtl && mWidgets.size() > 1) {
              mWidget = mWidgets.get(mWidgets.size() - 1).mWidget;
          }
          mChainStyle = orientation == HORIZONTAL
                  ? mWidget.getHorizontalChainStyle() : mWidget.getVerticalChainStyle();
      }
  
  
      @Override
      void clear() {
          mRunGroup = null;
          for (WidgetRun run : mWidgets) {
              run.clear();
          }
      }
  
      @Override
      void reset() {
          start.resolved = false;
          end.resolved = false;
      }
  
      @Override
      public void update(Dependency dependency) {
          if (!(start.resolved && end.resolved)) {
              return;
          }
  
          ConstraintWidget parent = mWidget.getParent();
          boolean isInRtl = false;
          if (parent instanceof ConstraintWidgetContainer) {
              isInRtl = ((ConstraintWidgetContainer) parent).isRtl();
          }
          int distance = end.value - start.value;
          int size = 0;
          int numMatchConstraints = 0;
          float weights = 0;
          int numVisibleWidgets = 0;
          final int count = mWidgets.size();
          // let's find the first visible widget...
          int firstVisibleWidget = -1;
          for (int i = 0; i < count; i++) {
              WidgetRun run = mWidgets.get(i);
              if (run.mWidget.getVisibility() == GONE) {
                  continue;
              }
              firstVisibleWidget = i;
              break;
          }
          // now the last visible widget...
          int lastVisibleWidget = -1;
          for (int i = count - 1; i >= 0; i--) {
              WidgetRun run = mWidgets.get(i);
              if (run.mWidget.getVisibility() == GONE) {
                  continue;
              }
              lastVisibleWidget = i;
              break;
          }
          for (int j = 0; j < 2; j++) {
              for (int i = 0; i < count; i++) {
                  WidgetRun run = mWidgets.get(i);
                  if (run.mWidget.getVisibility() == GONE) {
                      continue;
                  }
                  numVisibleWidgets++;
                  if (i > 0 && i >= firstVisibleWidget) {
                      size += run.start.mMargin;
                  }
                  int dimension = run.mDimension.value;
                  boolean treatAsFixed = run.mDimensionBehavior != MATCH_CONSTRAINT;
                  if (treatAsFixed) {
                      if (orientation == HORIZONTAL
                              && !run.mWidget.mHorizontalRun.mDimension.resolved) {
                          return;
                      }
                      if (orientation == VERTICAL && !run.mWidget.mVerticalRun.mDimension.resolved) {
                          return;
                      }
                  } else if (run.matchConstraintsType == MATCH_CONSTRAINT_WRAP && j == 0) {
                      treatAsFixed = true;
                      dimension = run.mDimension.wrapValue;
                      numMatchConstraints++;
                  } else if (run.mDimension.resolved) {
                      treatAsFixed = true;
                  }
                  if (!treatAsFixed) { // only for the first pass
                      numMatchConstraints++;
                      float weight = run.mWidget.mWeight[orientation];
                      if (weight >= 0) {
                          weights += weight;
                      }
                  } else {
                      size += dimension;
                  }
                  if (i < count - 1 && i < lastVisibleWidget) {
                      size += -run.end.mMargin;
                  }
              }
              if (size < distance || numMatchConstraints == 0) {
                  break; // we are good to go!
              }
              // otherwise, let's do another pass with using match_constraints
              numVisibleWidgets = 0;
              numMatchConstraints = 0;
              size = 0;
              weights = 0;
          }
  
          int position = start.value;
          if (isInRtl) {
              position = end.value;
          }
          if (size > distance) {
              if (isInRtl) {
                  position += (int) (0.5f + (size - distance) / 2f);
              } else {
                  position -= (int) (0.5f + (size - distance) / 2f);
              }
          }
          int matchConstraintsDimension = 0;
          if (numMatchConstraints > 0) {
              matchConstraintsDimension =
                      (int) (0.5f + (distance - size) / (float) numMatchConstraints);
  
              int appliedLimits = 0;
              for (int i = 0; i < count; i++) {
                  WidgetRun run = mWidgets.get(i);
                  if (run.mWidget.getVisibility() == GONE) {
                      continue;
                  }
                  if (run.mDimensionBehavior == MATCH_CONSTRAINT && !run.mDimension.resolved) {
                      int dimension = matchConstraintsDimension;
                      if (weights > 0) {
                          float weight = run.mWidget.mWeight[orientation];
                          dimension = (int) (0.5f + weight * (distance - size) / weights);
                      }
                      int max;
                      int min;
                      int value = dimension;
                      if (orientation == HORIZONTAL) {
                          max = run.mWidget.mMatchConstraintMaxWidth;
                          min = run.mWidget.mMatchConstraintMinWidth;
                      } else {
                          max = run.mWidget.mMatchConstraintMaxHeight;
                          min = run.mWidget.mMatchConstraintMinHeight;
                      }
                      if (run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
                          value = Math.min(value, run.mDimension.wrapValue);
                      }
                      value = Math.max(min, value);
                      if (max > 0) {
                          value = Math.min(max, value);
                      }
                      if (value != dimension) {
                          appliedLimits++;
                          dimension = value;
                      }
                      run.mDimension.resolve(dimension);
                  }
              }
              if (appliedLimits > 0) {
                  numMatchConstraints -= appliedLimits;
                  // we have to recompute the sizes
                  size = 0;
                  for (int i = 0; i < count; i++) {
                      WidgetRun run = mWidgets.get(i);
                      if (run.mWidget.getVisibility() == GONE) {
                          continue;
                      }
                      if (i > 0 && i >= firstVisibleWidget) {
                          size += run.start.mMargin;
                      }
                      size += run.mDimension.value;
                      if (i < count - 1 && i < lastVisibleWidget) {
                          size += -run.end.mMargin;
                      }
                  }
              }
              if (mChainStyle == ConstraintWidget.CHAIN_PACKED && appliedLimits == 0) {
                  mChainStyle = ConstraintWidget.CHAIN_SPREAD;
              }
          }
  
          if (size > distance) {
              mChainStyle = ConstraintWidget.CHAIN_PACKED;
          }
  
          if (numVisibleWidgets > 0 && numMatchConstraints == 0
                  && firstVisibleWidget == lastVisibleWidget) {
              // only one widget of fixed size to display...
              mChainStyle = ConstraintWidget.CHAIN_PACKED;
          }
  
          if (mChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE) {
              int gap = 0;
              if (numVisibleWidgets > 1) {
                  gap = (distance - size) / (numVisibleWidgets - 1);
              } else if (numVisibleWidgets == 1) {
                  gap = (distance - size) / 2;
              }
              if (numMatchConstraints > 0) {
                  gap = 0;
              }
              for (int i = 0; i < count; i++) {
                  int index = i;
                  if (isInRtl) {
                      index = count - (i + 1);
                  }
                  WidgetRun run = mWidgets.get(index);
                  if (run.mWidget.getVisibility() == GONE) {
                      run.start.resolve(position);
                      run.end.resolve(position);
                      continue;
                  }
                  if (i > 0) {
                      if (isInRtl) {
                          position -= gap;
                      } else {
                          position += gap;
                      }
                  }
                  if (i > 0 && i >= firstVisibleWidget) {
                      if (isInRtl) {
                          position -= run.start.mMargin;
                      } else {
                          position += run.start.mMargin;
                      }
                  }
  
                  if (isInRtl) {
                      run.end.resolve(position);
                  } else {
                      run.start.resolve(position);
                  }
  
                  int dimension = run.mDimension.value;
                  if (run.mDimensionBehavior == MATCH_CONSTRAINT
                          && run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
                      dimension = run.mDimension.wrapValue;
                  }
                  if (isInRtl) {
                      position -= dimension;
                  } else {
                      position += dimension;
                  }
  
                  if (isInRtl) {
                      run.start.resolve(position);
                  } else {
                      run.end.resolve(position);
                  }
                  run.mResolved = true;
                  if (i < count - 1 && i < lastVisibleWidget) {
                      if (isInRtl) {
                          position -= -run.end.mMargin;
                      } else {
                          position += -run.end.mMargin;
                      }
                  }
              }
          } else if (mChainStyle == ConstraintWidget.CHAIN_SPREAD) {
              int gap = (distance - size) / (numVisibleWidgets + 1);
              if (numMatchConstraints > 0) {
                  gap = 0;
              }
              for (int i = 0; i < count; i++) {
                  int index = i;
                  if (isInRtl) {
                      index = count - (i + 1);
                  }
                  WidgetRun run = mWidgets.get(index);
                  if (run.mWidget.getVisibility() == GONE) {
                      run.start.resolve(position);
                      run.end.resolve(position);
                      continue;
                  }
                  if (isInRtl) {
                      position -= gap;
                  } else {
                      position += gap;
                  }
                  if (i > 0 && i >= firstVisibleWidget) {
                      if (isInRtl) {
                          position -= run.start.mMargin;
                      } else {
                          position += run.start.mMargin;
                      }
                  }
  
                  if (isInRtl) {
                      run.end.resolve(position);
                  } else {
                      run.start.resolve(position);
                  }
  
                  int dimension = run.mDimension.value;
                  if (run.mDimensionBehavior == MATCH_CONSTRAINT
                          && run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
                      dimension = Math.min(dimension, run.mDimension.wrapValue);
                  }
  
                  if (isInRtl) {
                      position -= dimension;
                  } else {
                      position += dimension;
                  }
  
                  if (isInRtl) {
                      run.start.resolve(position);
                  } else {
                      run.end.resolve(position);
                  }
                  if (i < count - 1 && i < lastVisibleWidget) {
                      if (isInRtl) {
                          position -= -run.end.mMargin;
                      } else {
                          position += -run.end.mMargin;
                      }
                  }
              }
          } else if (mChainStyle == ConstraintWidget.CHAIN_PACKED) {
              float bias = (orientation == HORIZONTAL) ? mWidget.getHorizontalBiasPercent()
                      : mWidget.getVerticalBiasPercent();
              if (isInRtl) {
                  bias = 1 - bias;
              }
              int gap = (int) (0.5f + (distance - size) * bias);
              if (gap < 0 || numMatchConstraints > 0) {
                  gap = 0;
              }
              if (isInRtl) {
                  position -= gap;
              } else {
                  position += gap;
              }
              for (int i = 0; i < count; i++) {
                  int index = i;
                  if (isInRtl) {
                      index = count - (i + 1);
                  }
                  WidgetRun run = mWidgets.get(index);
                  if (run.mWidget.getVisibility() == GONE) {
                      run.start.resolve(position);
                      run.end.resolve(position);
                      continue;
                  }
                  if (i > 0 && i >= firstVisibleWidget) {
                      if (isInRtl) {
                          position -= run.start.mMargin;
                      } else {
                          position += run.start.mMargin;
                      }
                  }
                  if (isInRtl) {
                      run.end.resolve(position);
                  } else {
                      run.start.resolve(position);
                  }
  
                  int dimension = run.mDimension.value;
                  if (run.mDimensionBehavior == MATCH_CONSTRAINT
                          && run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
                      dimension = run.mDimension.wrapValue;
                  }
                  if (isInRtl) {
                      position -= dimension;
                  } else {
                      position += dimension;
                  }
  
                  if (isInRtl) {
                      run.start.resolve(position);
                  } else {
                      run.end.resolve(position);
                  }
                  if (i < count - 1 && i < lastVisibleWidget) {
                      if (isInRtl) {
                          position -= -run.end.mMargin;
                      } else {
                          position += -run.end.mMargin;
                      }
                  }
              }
          }
      }
  
      // @TODO: add description
      @Override
      public void applyToWidget() {
          for (int i = 0; i < mWidgets.size(); i++) {
              WidgetRun run = mWidgets.get(i);
              run.applyToWidget();
          }
      }
  
      private ConstraintWidget getFirstVisibleWidget() {
          for (int i = 0; i < mWidgets.size(); i++) {
              WidgetRun run = mWidgets.get(i);
              if (run.mWidget.getVisibility() != GONE) {
                  return run.mWidget;
              }
          }
          return null;
      }
  
      private ConstraintWidget getLastVisibleWidget() {
          for (int i = mWidgets.size() - 1; i >= 0; i--) {
              WidgetRun run = mWidgets.get(i);
              if (run.mWidget.getVisibility() != GONE) {
                  return run.mWidget;
              }
          }
          return null;
      }
  
  
      @Override
      void apply() {
          for (WidgetRun run : mWidgets) {
              run.apply();
          }
          int count = mWidgets.size();
          if (count < 1) {
              return;
          }
  
          // get the first and last element of the chain
          ConstraintWidget firstWidget = mWidgets.get(0).mWidget;
          ConstraintWidget lastWidget = mWidgets.get(count - 1).mWidget;
  
          if (orientation == HORIZONTAL) {
              ConstraintAnchor startAnchor = firstWidget.mLeft;
              ConstraintAnchor endAnchor = lastWidget.mRight;
              DependencyNode startTarget = getTarget(startAnchor, HORIZONTAL);
              int startMargin = startAnchor.getMargin();
              ConstraintWidget firstVisibleWidget = getFirstVisibleWidget();
              if (firstVisibleWidget != null) {
                  startMargin = firstVisibleWidget.mLeft.getMargin();
              }
              if (startTarget != null) {
                  addTarget(start, startTarget, startMargin);
              }
              DependencyNode endTarget = getTarget(endAnchor, HORIZONTAL);
              int endMargin = endAnchor.getMargin();
              ConstraintWidget lastVisibleWidget = getLastVisibleWidget();
              if (lastVisibleWidget != null) {
                  endMargin = lastVisibleWidget.mRight.getMargin();
              }
              if (endTarget != null) {
                  addTarget(end, endTarget, -endMargin);
              }
          } else {
              ConstraintAnchor startAnchor = firstWidget.mTop;
              ConstraintAnchor endAnchor = lastWidget.mBottom;
              DependencyNode startTarget = getTarget(startAnchor, VERTICAL);
              int startMargin = startAnchor.getMargin();
              ConstraintWidget firstVisibleWidget = getFirstVisibleWidget();
              if (firstVisibleWidget != null) {
                  startMargin = firstVisibleWidget.mTop.getMargin();
              }
              if (startTarget != null) {
                  addTarget(start, startTarget, startMargin);
              }
              DependencyNode endTarget = getTarget(endAnchor, VERTICAL);
              int endMargin = endAnchor.getMargin();
              ConstraintWidget lastVisibleWidget = getLastVisibleWidget();
              if (lastVisibleWidget != null) {
                  endMargin = lastVisibleWidget.mBottom.getMargin();
              }
              if (endTarget != null) {
                  addTarget(end, endTarget, -endMargin);
              }
          }
          start.updateDelegate = this;
          end.updateDelegate = this;
      }
  
  }
  