content: /*
   * MIT License
   *
   * Copyright (c) 2016 Alibaba Group
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in all
   * copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   * SOFTWARE.
   */
  
  package com.alibaba.android.vlayout.layout;
  
  import com.alibaba.android.vlayout.LayoutHelper;
  import com.alibaba.android.vlayout.LayoutManagerHelper;
  import com.alibaba.android.vlayout.OrientationHelperEx;
  import com.alibaba.android.vlayout.R;
  import com.alibaba.android.vlayout.VirtualLayoutManager;
  import com.alibaba.android.vlayout.VirtualLayoutManager.LayoutStateWrapper;
  
  import android.graphics.Rect;
  import android.support.annotation.NonNull;
  import android.support.annotation.Nullable;
  import android.support.v7.widget.RecyclerView;
  import android.util.Log;
  import android.view.View;
  
  /**
   * {@link com.alibaba.android.vlayout.LayoutHelper} that provides basic methods
   */
  public abstract class BaseLayoutHelper extends MarginLayoutHelper {
  
      private static final String TAG = "BaseLayoutHelper";
  
      public static boolean DEBUG = false;
  
      protected Rect mLayoutRegion = new Rect();
  
      View mLayoutView;
  
      int mBgColor;
  
      float mAspectRatio = Float.NaN;
  
      public BaseLayoutHelper() {
  
      }
  
      @Override
      public boolean isFixLayout() {
          return false;
      }
  
      public int getBgColor() {
          return this.mBgColor;
      }
  
      /**
       * Set backgroundColor for LayoutView
       *
       * @param bgColor
       */
      public void setBgColor(int bgColor) {
          this.mBgColor = bgColor;
      }
  
      public void setAspectRatio(float aspectRatio) {
          this.mAspectRatio = aspectRatio;
      }
  
      public float getAspectRatio() {
          return mAspectRatio;
      }
  
      private int mItemCount = 0;
  
      /**
       * The number of items in current layout
       *
       * @return the number of child views
       */
      @Override
      public int getItemCount() {
          return mItemCount;
      }
  
      @Override
      public void setItemCount(int itemCount) {
          this.mItemCount = itemCount;
      }
  
      /**
       * Retrieve next view and add it into layout, this is to make sure that view are added by order
       *
       * @param recycler    recycler generate views
       * @param layoutState current layout state
       * @param helper      helper to add views
       * @param result      chunk result to tell layoutManager whether layout process goes end
       * @return next view to render, null if no more view available
       */
      @Nullable
      public final View nextView(RecyclerView.Recycler recycler, LayoutStateWrapper layoutState, LayoutManagerHelper helper, LayoutChunkResult result) {
          View view = layoutState.next(recycler);
          if (view == null) {
              // if we are laying out views in scrap, this may return null which means there is
              // no more items to layout.
              if (DEBUG && !layoutState.hasScrapList()) {
                  throw new RuntimeException("received null view when unexpected");
              }
              // if there is no more views can be retrieved, this layout process is finished
              result.mFinished = true;
              return null;
          }
  
          helper.addChildView(layoutState, view);
          return view;
      }
  
  
      @Override
      public void beforeLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
                               LayoutManagerHelper helper) {
          if (DEBUG) {
              Log.d(TAG, "call beforeLayout() on " + this.getClass().getSimpleName());
          }
  
  
          if (requireLayoutView()) {
              if (mLayoutView != null) {
                  // TODO: recycle LayoutView
                  // helper.detachChildView(mLayoutView);
              }
          } else {
              // if no layoutView is required, remove it
              if (mLayoutView != null) {
                  if (mLayoutViewUnBindListener != null) {
                      mLayoutViewUnBindListener.onUnbind(mLayoutView, this);
                  }
                  helper.removeChildView(mLayoutView);
                  mLayoutView = null;
              }
          }
      }
  
      /**
       * Tell whether the scrolled value is valid, if not, means it's a layout processing without scrolling
       *
       * @param scrolled value of how many pixels does scrolled
       * @return true means during a scrolling process, false means during a layout process.
       */
      protected boolean isValidScrolled(int scrolled) {
          return scrolled != Integer.MAX_VALUE && scrolled != Integer.MIN_VALUE;
      }
  
  
      @Override
      public void afterLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
                              int startPosition, int endPosition, int scrolled,
                              LayoutManagerHelper helper) {
          if (DEBUG) {
              Log.d(TAG, "call afterLayout() on " + this.getClass().getSimpleName());
          }
  
  
          if (requireLayoutView()) {
              if (isValidScrolled(scrolled) && mLayoutView != null) {
                  // initial layout do reset
                  mLayoutRegion.union(mLayoutView.getLeft(), mLayoutView.getTop(), mLayoutView.getRight(), mLayoutView.getBottom());
              }
  
  
              if (!mLayoutRegion.isEmpty()) {
                  if (isValidScrolled(scrolled)) {
                      if (helper.getOrientation() == VirtualLayoutManager.VERTICAL) {
                          mLayoutRegion.offset(0, -scrolled);
                      } else {
                          mLayoutRegion.offset(-scrolled, 0);
                      }
                  }
                  int contentWidth = helper.getContentWidth();
                  int contentHeight = helper.getContentHeight();
                  if (helper.getOrientation() == VirtualLayoutManager.VERTICAL ?
                          mLayoutRegion.intersects(0, -contentHeight / 4, contentWidth, contentHeight + contentHeight / 4) :
                          mLayoutRegion.intersects(-contentWidth / 4, 0, contentWidth + contentWidth / 4, contentHeight)) {
  
                      if (mLayoutView == null) {
                          mLayoutView = helper.generateLayoutView();
                          helper.addOffFlowView(mLayoutView, true);
                      }
                      //finally fix layoutRegion's height and with here to avoid visual blank
                      if (helper.getOrientation() == VirtualLayoutManager.VERTICAL) {
                          mLayoutRegion.left = helper.getPaddingLeft() + mMarginLeft;
                          mLayoutRegion.right = helper.getContentWidth() - helper.getPaddingRight() - mMarginRight;
                      } else {
                          mLayoutRegion.top = helper.getPaddingTop() + mMarginTop;
                          mLayoutRegion.bottom = helper.getContentHeight() - helper.getPaddingBottom() - mMarginBottom;
                      }
  
                      bindLayoutView(mLayoutView);
                      return;
                  } else {
                      mLayoutRegion.set(0, 0, 0, 0);
                      if (mLayoutView != null) {
                          mLayoutView.layout(0, 0, 0, 0);
                      }
                  }
              }
          }
  
          if (mLayoutView != null) {
              if (mLayoutViewUnBindListener != null) {
                  mLayoutViewUnBindListener.onUnbind(mLayoutView, this);
              }
              helper.removeChildView(mLayoutView);
              mLayoutView = null;
          }
  
      }
  
      @Override
      public void adjustLayout(int startPosition, int endPosition, LayoutManagerHelper helper) {
          if (requireLayoutView()) {
              View refer = null;
              Rect tempRect = new Rect();
              final OrientationHelperEx orientationHelper = helper.getMainOrientationHelper();
              for (int i = 0; i < helper.getChildCount(); i++) {
                  refer = helper.getChildAt(i);
                  int anchorPos = helper.getPosition(refer);
                  if (getRange().contains(anchorPos)) {
                      if (refer.getVisibility() == View.GONE) {
                          tempRect.setEmpty();
                      } else {
                          final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
                              refer.getLayoutParams();
                          if (helper.getOrientation() == VirtualLayoutManager.VERTICAL) {
                              tempRect.union(helper.getDecoratedLeft(refer) - params.leftMargin,
                                  orientationHelper.getDecoratedStart(refer),
                                  helper.getDecoratedRight(refer) + params.rightMargin,
                                  orientationHelper.getDecoratedEnd(refer));
                          } else {
                              tempRect.union(orientationHelper.getDecoratedStart(refer),
                                  helper.getDecoratedTop(refer) - params.topMargin, orientationHelper.getDecoratedEnd(refer),
                                  helper.getDecoratedBottom(refer) + params.bottomMargin);
                          }
                      }
                  }
              }
              if (!tempRect.isEmpty()) {
                  mLayoutRegion.set(tempRect.left - mPaddingLeft, tempRect.top - mPaddingTop,
                      tempRect.right + mPaddingRight, tempRect.bottom + mPaddingBottom);
              } else {
                  mLayoutRegion.setEmpty();
              }
              if (mLayoutView != null) {
                  mLayoutView.layout(mLayoutRegion.left, mLayoutRegion.top, mLayoutRegion.right, mLayoutRegion.bottom);
              }
          }
      }
  
      /**
       * Called when {@link com.alibaba.android.vlayout.LayoutHelper} get dropped
       * Do default clean jobs defined by framework
       *
       * @param helper LayoutManagerHelper
       */
      @Override
      public final void clear(LayoutManagerHelper helper) {
          // remove LayoutViews if there is one
          if (mLayoutView != null) {
              if (mLayoutViewUnBindListener != null) {
                  mLayoutViewUnBindListener.onUnbind(mLayoutView, this);
              }
              helper.removeChildView(mLayoutView);
              mLayoutView = null;
          }
  
          // call user defined
          onClear(helper);
      }
  
      /**
       * Called when {@link com.alibaba.android.vlayout.LayoutHelper} get dropped, do clean custom jobs
       *
       * @param helper
       */
      protected void onClear(LayoutManagerHelper helper) {
  
      }
  
      /**
       * @return
       */
      @Override
      public boolean requireLayoutView() {
          return mBgColor != 0 || mLayoutViewBindListener != null;
      }
  
      public abstract void layoutViews(RecyclerView.Recycler recycler, RecyclerView.State state,
                                       LayoutStateWrapper layoutState, LayoutChunkResult result,
                                       LayoutManagerHelper helper);
  
  
      @Override
      public void doLayout(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutStateWrapper layoutState, LayoutChunkResult result, LayoutManagerHelper helper) {
          layoutViews(recycler, state, layoutState, result, helper);
      }
  
      /**
       * Helper function which do layout children and also update layoutRegion
       * but it won't consider margin in layout, so you need take care of margin if you apply margin to your layoutView
       *
       * @param child  child that will be laid
       * @param left   left position
       * @param top    top position
       * @param right  right position
       * @param bottom bottom position
       * @param helper layoutManagerHelper, help to lay child
       */
      protected void layoutChildWithMargin(final View child, int left, int top, int right, int bottom, @NonNull LayoutManagerHelper helper) {
          layoutChildWithMargin(child, left, top, right, bottom, helper, false);
      }
  
      protected void layoutChildWithMargin(final View child, int left, int top, int right, int bottom, @NonNull LayoutManagerHelper helper, boolean addLayoutRegionWithMargin) {
          helper.layoutChildWithMargins(child, left, top, right, bottom);
          if (requireLayoutView()) {
              if (addLayoutRegionWithMargin) {
                  mLayoutRegion
                          .union(left - mPaddingLeft - mMarginLeft, top - mPaddingTop - mMarginTop,
                                  right + mPaddingRight + mMarginRight,
                                  bottom + mPaddingBottom + mMarginBottom);
              } else {
                  mLayoutRegion.union(left - mPaddingLeft, top - mPaddingTop, right + mPaddingRight, bottom + mPaddingBottom);
              }
          }
  
      }
  
      /**
       * Helper function which do layout children and also update layoutRegion
       *
       * @param child  child that will be laid
       * @param left   left position
       * @param top    top position
       * @param right  right position
       * @param bottom bottom position
       * @param helper layoutManagerHelper, help to lay child
       */
      protected void layoutChild(final View child, int left, int top, int right, int bottom, @NonNull LayoutManagerHelper helper) {
          layoutChild(child, left, top, right, bottom, helper, false);
      }
  
      protected void layoutChild(final View child, int left, int top, int right, int bottom, @NonNull LayoutManagerHelper helper, boolean addLayoutRegionWithMargin) {
          helper.layoutChild(child, left, top, right, bottom);
          if (requireLayoutView()) {
              if (addLayoutRegionWithMargin) {
                  mLayoutRegion
                          .union(left - mPaddingLeft - mMarginLeft, top - mPaddingTop - mMarginTop,
                                  right + mPaddingRight + mMarginRight,
                                  bottom + mPaddingBottom + mMarginBottom);
              } else {
                  mLayoutRegion.union(left - mPaddingLeft, top - mPaddingTop, right + mPaddingRight, bottom + mPaddingBottom);
              }
          }
  
      }
  
      /**
       * Listener to handle LayoutViews, like bgImage
       */
      public interface LayoutViewBindListener {
          void onBind(View layoutView, BaseLayoutHelper baseLayoutHelper);
      }
  
      /**
       * Listener to handle LayoutViews, like bgImage
       */
      public interface LayoutViewUnBindListener {
          void onUnbind(View layoutView, BaseLayoutHelper baseLayoutHelper);
      }
  
  
      public interface LayoutViewHelper {
  
          /**
           * Implement it by maintaining a map between layoutView and image url or setting a unique tag to view. It's up to your choice.
           * @param layoutView view ready to be binded with an image
  *        * @param id layoutView's identifier
           */
          void onBindViewSuccess(View layoutView, String id);
      }
  
      private LayoutViewUnBindListener mLayoutViewUnBindListener;
  
      private LayoutViewBindListener mLayoutViewBindListener;
  
      /**
       * Helper to decide whether call {@link LayoutViewBindListener#onBind(View, BaseLayoutHelper)}.
       * Here is a performance issue: {@link LayoutViewBindListener#onBind(View, BaseLayoutHelper)} is called during layout phase,
       * when binding image to it would cause view tree to relayout, then the same  {@link LayoutViewBindListener#onBind(View, BaseLayoutHelper)} would be called.
       * User should provide enough information to tell LayoutHelper whether image has been bind success.
       * If image has been successfully binded , no more dead loop happens.
       *
       * Of course you can handle this logic by yourself and ignore this helper.
       */
      public static class DefaultLayoutViewHelper implements LayoutViewBindListener, LayoutViewUnBindListener, LayoutViewHelper {
  
          private final LayoutViewBindListener mLayoutViewBindListener;
  
          private final LayoutViewUnBindListener mLayoutViewUnBindListener;
  
          public DefaultLayoutViewHelper(
              LayoutViewBindListener layoutViewBindListener,
              LayoutViewUnBindListener layoutViewUnBindListener) {
              mLayoutViewBindListener = layoutViewBindListener;
              mLayoutViewUnBindListener = layoutViewUnBindListener;
          }
  
          @Override
          public void onBindViewSuccess(View layoutView, String id) {
              layoutView.setTag(R.id.tag_layout_helper_bg, id);
          }
  
          @Override
          public void onBind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
              if (layoutView.getTag(R.id.tag_layout_helper_bg) == null) {
                  if (mLayoutViewBindListener != null) {
                      mLayoutViewBindListener.onBind(layoutView, baseLayoutHelper);
                  }
              }
          }
  
          @Override
          public void onUnbind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
              if (mLayoutViewUnBindListener != null) {
                  mLayoutViewUnBindListener.onUnbind(layoutView, baseLayoutHelper);
              }
              layoutView.setTag(R.id.tag_layout_helper_bg, null);
          }
      }
  
      public void setLayoutViewHelper(DefaultLayoutViewHelper layoutViewHelper) {
          mLayoutViewBindListener = layoutViewHelper;
          mLayoutViewUnBindListener = layoutViewHelper;
      }
  
      /**
       * Better to use {@link #setLayoutViewHelper(DefaultLayoutViewHelper)}
       * @param bindListener
       */
      public void setLayoutViewBindListener(LayoutViewBindListener bindListener) {
          mLayoutViewBindListener = bindListener;
      }
  
      /**
       * Better to use {@link #setLayoutViewHelper(DefaultLayoutViewHelper)}
       * @param layoutViewUnBindListener
       */
      public void setLayoutViewUnBindListener(
              LayoutViewUnBindListener layoutViewUnBindListener) {
          mLayoutViewUnBindListener = layoutViewUnBindListener;
      }
  
      @Override
      public void bindLayoutView(@NonNull final View layoutView) {
          layoutView.measure(View.MeasureSpec.makeMeasureSpec(mLayoutRegion.width(), View.MeasureSpec.EXACTLY),
                  View.MeasureSpec.makeMeasureSpec(mLayoutRegion.height(), View.MeasureSpec.EXACTLY));
          layoutView.layout(mLayoutRegion.left, mLayoutRegion.top, mLayoutRegion.right, mLayoutRegion.bottom);
          layoutView.setBackgroundColor(mBgColor);
  
          if (mLayoutViewBindListener != null) {
              mLayoutViewBindListener.onBind(layoutView, this);
          }
  
          // reset region rectangle
          mLayoutRegion.set(0, 0, 0, 0);
      }
  
      protected void handleStateOnResult(LayoutChunkResult result, View view) {
          if (view == null) {
              return;
          }
  
          RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
  
          // Consume the available space if the view is not removed OR changed
          if (params.isItemRemoved() || params.isItemChanged()) {
              result.mIgnoreConsumed = true;
          }
  
          // used when search a focusable view
          result.mFocusable = result.mFocusable || view.isFocusable();
  
      }
  
      /**
       * Helper methods to handle focus states for views
       * @param result
       * @param views
       */
      protected void handleStateOnResult(LayoutChunkResult result, View[] views) {
          if (views == null) return;
  
          for (int i = 0; i < views.length; i++) {
              View view = views[i];
              if (view == null) {
                  continue;
              }
              RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
  
              // Consume the available space if the view is not removed OR changed
              if (params.isItemRemoved() || params.isItemChanged()) {
                  result.mIgnoreConsumed = true;
              }
  
              // used when search a focusable view
              result.mFocusable = result.mFocusable || view.isFocusable();
  
              if (result.mFocusable && result.mIgnoreConsumed) {
                  break;
              }
          }
      }
  
      protected int computeStartSpace(LayoutManagerHelper helper, boolean layoutInVertical, boolean isLayoutEnd, boolean isOverLapMargin) {
          int startSpace = 0;
          LayoutHelper lastLayoutHelper = null;
          if (helper instanceof VirtualLayoutManager) {
              lastLayoutHelper = ((VirtualLayoutManager) helper).findNeighbourNonfixLayoutHelper(this, isLayoutEnd);
          }
          MarginLayoutHelper lastMarginLayoutHelper = null;
  
          if (lastLayoutHelper != null && lastLayoutHelper instanceof MarginLayoutHelper) {
              lastMarginLayoutHelper = (MarginLayoutHelper) lastLayoutHelper;
          }
          if (lastLayoutHelper == this)
              return 0;
  
          if (!isOverLapMargin) {
              startSpace = layoutInVertical ? mMarginTop + mPaddingTop : mMarginLeft + mPaddingLeft;
          } else {
              int offset = 0;
  
              if (lastMarginLayoutHelper == null) {
                  offset = layoutInVertical ? mMarginTop + mPaddingTop : mMarginLeft + mPaddingLeft;
              } else {
                  offset = layoutInVertical
                          ? (isLayoutEnd ? calGap(lastMarginLayoutHelper.mMarginBottom, mMarginTop) : calGap(lastMarginLayoutHelper.mMarginTop, mMarginBottom))
                          : (isLayoutEnd ? calGap(lastMarginLayoutHelper.mMarginRight, mMarginLeft) : calGap(lastMarginLayoutHelper.mMarginLeft, mMarginRight));
              }
              //Log.e("huang", "computeStartSpace offset: " + offset + ", isLayoutEnd: " + isLayoutEnd + ", " + this);
              startSpace += layoutInVertical
                      ? (isLayoutEnd ? mPaddingTop : mPaddingBottom)
                      : (isLayoutEnd ? mPaddingLeft : mPaddingRight);
  
              startSpace += offset;
          }
          return startSpace;
      }
  
      protected int computeEndSpace(LayoutManagerHelper helper, boolean layoutInVertical, boolean isLayoutEnd, boolean isOverLapMargin) {
          int endSpace = layoutInVertical
                  ? mMarginBottom + mPaddingBottom : mMarginLeft + mPaddingLeft;
          //Log.e("huang", "computeEndSpace offset: " + endSpace + ", isLayoutEnd: " + isLayoutEnd + ", " + this);
          //Log.e("huang", "===================\n\n");
          return endSpace;
      }
  
      private int calGap(int gap, int currGap) {
          if (gap < currGap) {
              return currGap - gap;
          } else {
              return 0;
          }
      }
  }
  