content: /*
   * MIT License
   *
   * Copyright (c) 2018 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.tmall.wireless.tangram.structure.card;
  
  import android.support.annotation.NonNull;
  import android.support.annotation.Nullable;
  import android.text.TextUtils;
  import android.util.Log;
  import android.view.View;
  
  import com.alibaba.android.vlayout.LayoutHelper;
  import com.alibaba.android.vlayout.Range;
  import com.alibaba.android.vlayout.layout.BaseLayoutHelper;
  import com.alibaba.android.vlayout.layout.GridLayoutHelper;
  import com.alibaba.android.vlayout.layout.RangeGridLayoutHelper;
  import com.alibaba.android.vlayout.layout.RangeGridLayoutHelper.GridRangeStyle;
  import com.tmall.wireless.tangram.MVHelper;
  import com.tmall.wireless.tangram.dataparser.concrete.Card;
  import com.tmall.wireless.tangram.dataparser.concrete.Style;
  import com.tmall.wireless.tangram.structure.BaseCell;
  import com.tmall.wireless.tangram.support.CardSupport;
  
  import org.json.JSONArray;
  import org.json.JSONException;
  import org.json.JSONObject;
  
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  
  /**
   * Card perform grid layout
   *
   * @author villadora
   * @since 1.0.0
   */
  public class GridCard extends Card {
  
      private static final String TAG = "GridCard";
  
      private int mColumn = 0;
  
      public GridCard() {
      }
  
      public GridCard(int column) {
          this.mColumn = column;
      }
  
      @Override
      protected void parseHeaderCell(@NonNull MVHelper resolver, @Nullable JSONObject header) {
          BaseCell mHeader = createCell(this, resolver, header, serviceManager, true);
          ensureBlock(mHeader);
      }
  
      @Override
      protected void parseFooterCell(@NonNull MVHelper resolver, @Nullable JSONObject footer) {
          BaseCell mFooter = createCell(this, resolver, footer, serviceManager, true);
          ensureBlock(mFooter);
      }
  
      @Override
      public void addChildCard(Card card) {
          if (card == null) {
              return;
          }
          List<BaseCell> subCells = card.getCells();
          if (subCells != null && !subCells.isEmpty()) {
              addCells(card.getCells());
              int startOffset = mCells.indexOf(subCells.get(0));
              int endOffset = mCells.indexOf(subCells.get(subCells.size() - 1));
              Range range = Range.create(startOffset, endOffset);
              mChildren.put(range, card);
          }
      }
  
      @Override
      public void offsetChildCard(Card anchorCard, int offset) {
          if (anchorCard == null) {
              return;
          }
          ConcurrentHashMap<Range<Integer>, Card> newChildren = new ConcurrentHashMap<>();
          boolean startOffset = false;
          for (Map.Entry<Range<Integer>, Card> entry : mChildren.entrySet()) {
              Range<Integer> key = entry.getKey();
              Card child = entry.getValue();
              if (child == anchorCard) {
                  Range<Integer> newKey = Range.create(key.getLower().intValue(),
                          key.getUpper().intValue() + offset);
                  newChildren.put(newKey, child);
                  startOffset = true;
                  continue;
              }
              if (startOffset) {
                  Range<Integer> newKey = Range.create(key.getLower().intValue() + offset,
                          key.getUpper().intValue() + offset);
                  newChildren.put(newKey, child);
              } else {
                  newChildren.put(key, child);
              }
          }
          mChildren.clear();
          mChildren.putAll(newChildren);
      }
  
      @Override
      public void clearChildMap() {
          mChildren.clear();
      }
  
      public void clearCells() {
          mCells.clear();
      }
  
      @Override
      public void parseStyle(JSONObject data) {
          style = new GridStyle();
          if (data != null) {
              style.parseWith(data);
          }
          if (((GridStyle) style).column > 0) {
              mColumn = ((GridStyle) style).column;
          }
      }
  
      @Override
      public boolean isValid() {
          return super.isValid() && (mColumn > 0 || (style instanceof GridStyle && ((GridStyle) style).column > 0));
      }
  
      @Nullable
      @Override
      public LayoutHelper convertLayoutHelper(@Nullable LayoutHelper helper) {
          //create new layoutHelper to simplify recycling background view in vlayout
          RangeGridLayoutHelper gridHelper = new RangeGridLayoutHelper(1, mCells.size());
  
          gridHelper.setItemCount(mCells.size());
          gridHelper.setSpanCount(mColumn);
  
  
          // update style
          if (style instanceof GridStyle) {
              GridStyle gridStyle = (GridStyle) style;
  
              int totalColumn = mColumn;
              if (gridStyle.column > 0) {
                  totalColumn = gridStyle.column;
                  gridHelper.setSpanCount(gridStyle.column);
              }
  
              gridHelper.setSpanSizeLookup(new CellSpanSizeLookup(mCells, totalColumn));
  
              gridHelper.setVGap(gridStyle.vGap);
              gridHelper.setHGap(gridStyle.hGap);
              gridHelper.setAutoExpand(gridStyle.autoExpand);
  
              if (gridStyle.cols != null && gridStyle.cols.length > 0) {
                  gridHelper.setWeights(gridStyle.cols);
              }
  
              if (!Float.isNaN(gridStyle.aspectRatio)) {
                  gridHelper.setAspectRatio(gridStyle.aspectRatio);
              }
          }
  
          gridHelper.getRootRangeStyle().onClearChildMap();
          convertChildLayoutHelper(gridHelper, this);
  
          return gridHelper;
      }
  
      private void convertChildLayoutHelper(@Nullable RangeGridLayoutHelper gridHelper, GridCard parentCard) {
          for (Map.Entry<Range<Integer>, Card> entry : parentCard.getChildren().entrySet()) {
              Range<Integer> range = entry.getKey();
              Card child = entry.getValue();
              Style style = child.style;
              if (style instanceof GridStyle && child instanceof GridCard) {
                  final GridStyle gridStyle = (GridStyle) style;
                  final GridCard gridCard = (GridCard) child;
                  if (!gridCard.getChildren().isEmpty()) {
                      convertChildLayoutHelper(gridHelper, gridCard);
                  }
                  GridRangeStyle rangeStyle = new GridRangeStyle();
                  int totalColumn = gridCard.mColumn;
                  if (gridStyle.column > 0) {
                      totalColumn = gridStyle.column;
                      rangeStyle.setSpanCount(gridStyle.column);
                  } else {
                      rangeStyle.setSpanCount(totalColumn);
                  }
  
                  rangeStyle.setSpanSizeLookup(new CellSpanSizeLookup(gridCard.getCells(), totalColumn));
  
                  rangeStyle.setVGap(gridStyle.vGap);
                  rangeStyle.setHGap(gridStyle.hGap);
                  rangeStyle.setAutoExpand(gridStyle.autoExpand);
  
                  if (gridStyle.cols != null && gridStyle.cols.length > 0) {
                      rangeStyle.setWeights(gridStyle.cols);
                  }
  
                  if (!Float.isNaN(gridStyle.aspectRatio)) {
                      rangeStyle.setAspectRatio(gridStyle.aspectRatio);
                  }
  
                  rangeStyle.setBgColor(style.bgColor);
                  rangeStyle.setMargin(style.margin[Style.MARGIN_LEFT_INDEX], style.margin[Style.MARGIN_TOP_INDEX],
                          style.margin[Style.MARGIN_RIGHT_INDEX], style.margin[Style.MARGIN_BOTTOM_INDEX]);
                  rangeStyle.setPadding(style.padding[Style.MARGIN_LEFT_INDEX], style.padding[Style.MARGIN_TOP_INDEX],
                          style.padding[Style.MARGIN_RIGHT_INDEX], style.padding[Style.MARGIN_BOTTOM_INDEX]);
                  if (!TextUtils.isEmpty(style.bgImgUrl)) {
                      if (serviceManager != null && serviceManager.getService(CardSupport.class) != null) {
                          final CardSupport support = serviceManager.getService(CardSupport.class);
                          rangeStyle.setLayoutViewBindListener(new BindListener(style) {
                              @Override
                              public void onBind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
                                  support.onBindBackgroundView(layoutView, gridCard);
                              }
                          });
                          rangeStyle.setLayoutViewUnBindListener(new UnbindListener(style) {
                              @Override
                              public void onUnbind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
                                  support.onUnbindBackgroundView(layoutView, gridCard);
                              }
                          });
                      } else {
                          rangeStyle.setLayoutViewBindListener(new BindListener(style));
                          rangeStyle.setLayoutViewUnBindListener(new UnbindListener(style));
                      }
                  } else {
                      rangeStyle.setLayoutViewBindListener(null);
                      rangeStyle.setLayoutViewUnBindListener(null);
                  }
  
                  gridHelper.addRangeStyle(range.getLower().intValue(), range.getUpper().intValue(), rangeStyle);
              }
          }
      }
  
      private void ensureBlock(BaseCell cell) {
          if (cell.isValid()) {
              if (cell.style.extras == null) {
                  cell.style.extras = new JSONObject();
              }
              try {
                  cell.style.extras.put(Style.KEY_DISPLAY, Style.DISPLAY_BLOCK);
              } catch (JSONException e) {
                  Log.w(TAG, Log.getStackTraceString(e), e);
              }
          }
      }
  
      public static class GridStyle extends Style {
          public static final String KEY_COLUMN = "column";
  
          public static final String KEY_AUTO_EXPAND = "autoExpand";
  
          public static final String KEY_IGNORE_EXTRA = "ignoreExtra";
  
          public static final String KEY_H_GAP = "hGap";
          public static final String KEY_V_GAP = "vGap";
  
          public int vGap = 0;
          public int hGap = 0;
  
          public boolean autoExpand = false;
  
          public int column = 0;
  
          public float[] cols;
  
          @Override
          public void parseWith(JSONObject data) {
              super.parseWith(data);
  
              if (data != null) {
                  column = data.optInt(KEY_COLUMN, 0);
  
                  autoExpand = data.optBoolean(KEY_AUTO_EXPAND, false);
  
                  JSONArray jsonCols = data.optJSONArray(KEY_COLS);
                  if (jsonCols != null) {
                      cols = new float[jsonCols.length()];
                      for (int i = 0; i < cols.length; i++) {
                          cols[i] = (float) jsonCols.optDouble(i, 0);
                      }
                  } else {
                      cols = new float[0];
                  }
  
                  hGap = Style.parseSize(data.optString(KEY_H_GAP), 0);
                  vGap = Style.parseSize(data.optString(KEY_V_GAP), 0);
              }
          }
      }
  
      public static class CellSpanSizeLookup extends GridLayoutHelper.SpanSizeLookup {
  
          public static final String DISPLAY_INLINE = "inline";
          public static final String DISPLAY_BLOCK = "block";
  
          public static final String KEY_COLSPAN = "colspan";
          public static final String KEY_DISPLAY = "display";
  
          protected final List<BaseCell> mCells;
  
          private final int mTotalColumn;
  
  
          public CellSpanSizeLookup(List<BaseCell> cells, int totalColumn) {
              this.mCells = cells;
              this.mTotalColumn = totalColumn;
          }
  
          @Override
          public int getSpanSize(int position) {
              position = position - getStartPosition();
              if (position < 0 || position >= mCells.size()) {
                  return 0;
              }
  
              BaseCell cell = mCells.get(position);
  
              if (cell != null && cell.style != null && cell.style.extras != null) {
                  int column = cell.style.extras.optInt(KEY_COLSPAN, 1);
  
                  String display = cell.style.extras.optString(KEY_DISPLAY, DISPLAY_INLINE);
  
                  if (TextUtils.equals(DISPLAY_BLOCK, display)) {
                      return mTotalColumn;
                  }
  
                  return cell.style.extras.optInt(KEY_COLSPAN, 1);
              }
  
              return 1;
          }
  
      }
  
      private static class ChildCardMap {
  
          private int lastIndex = -1;
  
          private int[] mOffsetMap = new int[1024];
  
          private Card[] mCardMap = new Card[1024];
  
          public void addChild(int startOffset, int endOffset, Card card) {
              int index = lastIndex + 1;
              if (index < mCardMap.length) {
                  mCardMap[index] = card;
              } else {
                  int oldLength = mCardMap.length;
                  Card[] newCardMap = new Card[oldLength * 2];
                  System.arraycopy(mCardMap, 0, newCardMap, 0, oldLength);
                  mCardMap = newCardMap;
                  mCardMap[oldLength] = card;
                  index = oldLength;
  
                  oldLength = mOffsetMap.length;
                  int[] newOffsetMap = new int[oldLength * 2];
                  System.arraycopy(mOffsetMap, 0, newOffsetMap, 0, oldLength);
                  mOffsetMap = newOffsetMap;
              }
              lastIndex = index;
              for (int i = startOffset; i <= endOffset; i++) {
                  mOffsetMap[i] = index;
              }
          }
  
          public Card getChild(int offset) {
              return mCardMap[mOffsetMap[offset]];
          }
  
      }
  
  }