/*
 * 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.dataparser.concrete;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
import android.widget.ImageView;

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.FixAreaLayoutHelper;
import com.alibaba.android.vlayout.layout.MarginLayoutHelper;
import com.tmall.wireless.tangram.Engine;
import com.tmall.wireless.tangram.MVHelper;
import com.tmall.wireless.tangram.TangramBuilder;
import com.tmall.wireless.tangram.core.service.ServiceManager;
import com.tmall.wireless.tangram.structure.BaseCell;
import com.tmall.wireless.tangram.structure.card.BannerCard;
import com.tmall.wireless.tangram.structure.card.LinearScrollCard;
import com.tmall.wireless.tangram.support.CardSupport;
import com.tmall.wireless.tangram.support.ExposureSupport;
import com.tmall.wireless.tangram.util.ImageUtils;
import com.tmall.wireless.tangram.util.LogUtils;
import com.tmall.wireless.tangram.util.Preconditions;
import com.tmall.wireless.tangram.util.Utils;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Basic Card, which will represent LayoutHelpers
 * <p/>
 */
public abstract class Card extends ComponentLifecycle {

    public static final Card NaN = new NaNCard();

    private static final String TAG = "Card";

    public static final String KEY_TYPE = "type";

    public static final String KEY_STYLE = "style";

    public static final String KEY_ID = "id";

    public static final String KEY_ITEMS = "items";

    public static final String KEY_HEADER = "header";

    public static final String KEY_FOOTER = "footer";

    public static final String KEY_LOAD_TYPE = "loadType";

    public static final String KEY_LOADED = "loaded";

    public static final String KEY_API_LOAD = "load";

    public static final String KEY_HAS_MORE = "hasMore";

    public static final String KEY_API_LOAD_PARAMS = "loadParams";

    public static final String KEY_MAX_CHILDREN = "maxChildren";

    public static final int LOAD_TYPE_LOADMORE = 1;

    /**
     * card type, use {@link #stringType} instead
     */
    @Deprecated
    public int type;

    public String stringType;

    @Nullable
    public String id;

    @Nullable
    protected BaseCell mHeader;

    @Nullable
    protected BaseCell mFooter;

    @NonNull
    protected Map<Range<Integer>, Card> mChildren = new HashMap<>();

    @NonNull
    protected List<BaseCell> mCells = new ArrayList<>();

    @NonNull
    protected final List<BaseCell> mPendingCells = new ArrayList<>();

    @NonNull
    protected final List<BaseCell> mInQueueCells = new ArrayList<>();

    @Nullable
    public Style style;

    /**
     * if need load more
     */
    public boolean loadMore = false;

    /**
     * indicate if loading data now
     */
    public boolean loading = false;

    /**
     * page number used at loading more data
     */
    public int page;

    /**
     * api name of loading more data
     */
    public String load;

    /**
     * api params of loading more data
     */
    public JSONObject loadParams;

    /**
     * if data has been loaded
     */
    public boolean loaded = false;

    /**
     * if this card has more data
     */
    public boolean hasMore = false;

    /**
     * Not used
     */
    @Deprecated
    public int rowId;

    /**
     * the max child count
     */
    protected int maxChildren = Integer.MAX_VALUE;

    /**
     * serviceManager
     */
    @Nullable
    public ServiceManager serviceManager;

    @Nullable
    private Map<String, Object> mParams;

    public JSONObject extras = new JSONObject();

    public void setParams(@Nullable Map<String, Object> params) {
        mParams = params;
    }

    @Nullable
    public Map<String, Object> getParams() {
        return mParams == null ? Collections.<String, Object>emptyMap() : mParams;
    }

    public Card() {

    }

    public void setStringType(String type) {
        this.stringType = type;
        try {
            this.type = Integer.parseInt(type);
        } catch (NumberFormatException e) {
        }
    }

    public void parseWith(@NonNull JSONObject data, @NonNull final MVHelper resolver) {
        parseWith(data, resolver, true);
    }

    public void parseWith(@NonNull JSONObject data, @NonNull final MVHelper resolver, boolean isParseCell) {
        if (TangramBuilder.isPrintLog()) {
            if (serviceManager == null) {
                throw new RuntimeException("serviceManager is null when parsing card");
            }
        }

        this.extras = data;
        this.type = data.optInt(KEY_TYPE, type);
        this.stringType = data.optString(KEY_TYPE);
        id = data.optString(KEY_ID, id == null ? "" : id);

        loadMore = data.optInt(KEY_LOAD_TYPE, 0) == LOAD_TYPE_LOADMORE;
        //you should alway assign hasMore to indicate there's more data explicitly
        if (data.has(KEY_HAS_MORE)) {
            hasMore = data.optBoolean(KEY_HAS_MORE);
        } else {
            if (data.has(KEY_LOAD_TYPE)) {
                hasMore = data.optInt(KEY_LOAD_TYPE) == LOAD_TYPE_LOADMORE;
            }
        }
        load = data.optString(KEY_API_LOAD, null);
        loadParams = data.optJSONObject(KEY_API_LOAD_PARAMS);
        loaded = data.optBoolean(KEY_LOADED, false);

        maxChildren = data.optInt(KEY_MAX_CHILDREN, maxChildren);
        // parsing header
        if (isParseCell) {
            JSONObject header = data.optJSONObject(KEY_HEADER);
            parseHeaderCell(resolver, header);
        }

        // parsing body
        JSONArray componentArray = data.optJSONArray(KEY_ITEMS);
        if (componentArray != null && isParseCell) {
            final int cellLength = Math.min(componentArray.length(), maxChildren);
            for (int i = 0; i < cellLength; i++) {
                final JSONObject cellData = componentArray.optJSONObject(i);
                createCell(this, resolver, cellData, serviceManager, true);
            }
        }
        // parsing footer
        if (isParseCell) {
            JSONObject footer = data.optJSONObject(KEY_FOOTER);
            parseFooterCell(resolver, footer);
        }

        JSONObject styleJson = data.optJSONObject(KEY_STYLE);

        parseStyle(styleJson);

    }

    public static BaseCell createCell(@Nullable Card parent, @NonNull MVHelper resolver, @NonNull JSONObject cellData,
                                      @NonNull ServiceManager serviceManager, boolean appended) {
        if (cellData != null) {
            BaseCell cell = null;
            String cellType = cellData.optString(Card.KEY_TYPE);
            if ((resolver.resolver().getViewClass(cellType) != null) || Utils.isCard(cellData)) {
                if (resolver.resolver().isCompatibleType(cellType)) {
                    cell = Utils.newInstance(resolver.resolver().getCellClass(cellType));

                    //do not display when newInstance failed
                    if (cell == null) {
                        return BaseCell.NaN;
                    }

                    cell.serviceManager = serviceManager; // ensure service manager
                } else {
                    if (Utils.isCard(cellData)) {
                        switch (cellType) {
                            case TangramBuilder.TYPE_CONTAINER_FLOW:
                            case TangramBuilder.TYPE_CONTAINER_1C_FLOW:
                            case TangramBuilder.TYPE_CONTAINER_2C_FLOW:
                            case TangramBuilder.TYPE_CONTAINER_3C_FLOW:
                            case TangramBuilder.TYPE_CONTAINER_4C_FLOW:
                            case TangramBuilder.TYPE_CONTAINER_5C_FLOW:
                                CardResolver cardResolver = serviceManager.getService(CardResolver.class);
                                Card gridCard = cardResolver.create(cellType);
                                gridCard.serviceManager = serviceManager;
                                gridCard.parseWith(cellData, resolver);
                                parent.addChildCard(gridCard);
                                break;
                            case TangramBuilder.TYPE_CONTAINER_BANNER:
                                BannerCard bannerCard = new BannerCard();
                                bannerCard.serviceManager = serviceManager;
                                bannerCard.parseWith(cellData, resolver);
                                List<BaseCell> bannerChildren = bannerCard.getCells();
                                if (bannerChildren.size() > 0) {
                                    cell = bannerCard.getCells().get(0);
                                }
                                break;
                            case TangramBuilder.TYPE_CONTAINER_SCROLL:
                                LinearScrollCard linearScrollCard = new LinearScrollCard();
                                linearScrollCard.serviceManager = serviceManager;
                                linearScrollCard.parseWith(cellData, resolver);
                                List<BaseCell> scrollChildren = linearScrollCard.getCells();
                                if (scrollChildren.size() > 0) {
                                    cell = linearScrollCard.getCells().get(0);
                                }
                                break;
                        }
                        if (cell != null) {
                            cell.serviceManager = serviceManager;
                            if (parent != null) {
                                cell.parent = parent;
                                cell.parentId = parent.id;
                            }
                        } else {
                            return BaseCell.NaN;
                        }
                    } else {
                        cell = new BaseCell(cellType);
                        cell.serviceManager = serviceManager;
                        if (parent != null) {
                            cell.parent = parent;
                            cell.parentId = parent.id;
                        }
                    }
                }
                if (parent != null) {
                    parent.parseCell(resolver, cellData, cell, appended);
                } else {
                    resolver.parseCell(cell, cellData);
                }
                cell.setStringType(cellType);
                return cell;
            } else {
                //support virtual view at layout
                BaseCellBinderResolver componentBinderResolver = serviceManager.getService(BaseCellBinderResolver.class);
                if (componentBinderResolver.has(cellType)) {
                    cell = new BaseCell(cellType);
                    cell.serviceManager = serviceManager;
                    if (parent != null) {
                        cell.parent = parent;
                        cell.parentId = parent.id;
                        parent.parseCell(resolver, cellData, cell, appended);
                    } else {
                        resolver.parseCell(cell, cellData);
                    }
                    cell.setStringType(cellType);
                    return cell;
                } else {
                    return BaseCell.NaN;
                }
            }
        }
        return BaseCell.NaN;
    }

    protected void parseCell(@NonNull MVHelper resolver, @NonNull JSONObject data, @NonNull final BaseCell cell, boolean appended) {
        resolver.parseCell(cell, data);
        //noinspection unchecked
        if (appended && !addCellInternal(cell, false)) {
            if (TangramBuilder.isPrintLog())
                LogUtils.w(TAG, "Parse invalid cell with data: " + data.toString());
        }

    }

    protected void parseStyle(@Nullable JSONObject data) {
        style = new Style();
        style.parseWith(data);
    }


    /**
     * LayoutHelper for this card, it won't get update for now!!!, as Card can be replaced, or data can be changed
     * If you want to reset the LayoutHelper? Think why you want to do it, make the LayoutHelper unmodified is easy
     * Please replace the card instead of LayoutHelper.
     */
    private LayoutHelper mLayoutHelper = null;

    public LayoutHelper getExistLayoutHelper() {
        return mLayoutHelper;
    }

    /**
     * whether retain LayoutHelper once it created,
     */
    protected boolean mRetainLayout = true;

    @Nullable
    public final LayoutHelper getLayoutHelper() {

        LayoutHelper helper = convertLayoutHelper(mLayoutHelper);

        // bind style to helper
        if (style != null && helper != null) {
            helper.setZIndex(style.zIndex);

            if (helper instanceof BaseLayoutHelper) {
                BaseLayoutHelper baseHelper = (BaseLayoutHelper) helper;
                baseHelper.setBgColor(style.bgColor);
                if (!TextUtils.isEmpty(style.bgImgUrl)) {
                    if (serviceManager != null && serviceManager.getService(CardSupport.class) != null) {
                        final CardSupport support = serviceManager.getService(CardSupport.class);
                        baseHelper.setLayoutViewBindListener(new BindListener(style) {
                            @Override
                            public void onBind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
                                support.onBindBackgroundView(layoutView, Card.this);
                            }
                        });
                        baseHelper.setLayoutViewUnBindListener(new UnbindListener(style) {
                            @Override
                            public void onUnbind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
                                support.onUnbindBackgroundView(layoutView, Card.this);
                            }
                        });
                    } else {
                        baseHelper.setLayoutViewBindListener(new BindListener(style));
                        baseHelper.setLayoutViewUnBindListener(new UnbindListener(style));
                    }
                } else {
                    baseHelper.setLayoutViewBindListener(null);
                    baseHelper.setLayoutViewUnBindListener(null);
                }

                if (!Float.isNaN(style.aspectRatio)) {
                    // ((BaseLayoutHelper) helper).setAspectRatio(style.aspectRatio);
                }

            }

            if (helper instanceof FixAreaLayoutHelper) {
                FixAreaLayoutHelper fixHelper = (FixAreaLayoutHelper) helper;
                boolean hasCustomAnimatorHelper = false;
                if (serviceManager != null && serviceManager.getService(CardSupport.class) != null) {
                    CardSupport support = serviceManager.getService(CardSupport.class);
                    FixAreaLayoutHelper.FixViewAnimatorHelper viewAnimatorHelper = support.onGetFixViewAppearAnimator(Card.this);
                    if (viewAnimatorHelper != null) {
                        hasCustomAnimatorHelper = true;
                        fixHelper.setFixViewAnimatorHelper(viewAnimatorHelper);
                    }
                }
                if (!hasCustomAnimatorHelper) {
                    final int duration = style.extras != null ? style.extras.optInt(Style.KEY_ANIMATION_DURATION) : 0;
                    if (duration > 0) {
                        fixHelper.setFixViewAnimatorHelper(new FixAreaLayoutHelper.FixViewAnimatorHelper() {
                            @Override
                            public ViewPropertyAnimator onGetFixViewAppearAnimator(View fixView) {
                                int height = fixView.getMeasuredHeight();
                                fixView.setTranslationY(-height);
                                return fixView.animate().translationYBy(height).setDuration(duration);
                            }

                            @Override
                            public ViewPropertyAnimator onGetFixViewDisappearAnimator(View fixView) {
                                int height = fixView.getMeasuredHeight();
                                return fixView.animate().translationYBy(-height).setDuration(duration);
                            }
                        });
                    }
                }
            }

            if (helper instanceof MarginLayoutHelper) {
                ((MarginLayoutHelper) helper).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]);
                ((MarginLayoutHelper) helper).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 (mRetainLayout) {
            mLayoutHelper = helper;
        }

        return helper;
    }

    public static class BindListener implements BaseLayoutHelper.LayoutViewBindListener {
        private Style mStyle;

        public BindListener(Style style) {
            this.mStyle = style;
        }

        @Override
        public void onBind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
            if (mStyle != null && !TextUtils.isEmpty(mStyle.bgImgUrl)) {
                if (layoutView instanceof ImageView) {
                    ImageUtils.doLoadImageUrl((ImageView) layoutView, mStyle.bgImgUrl);
                }
            }
        }
    }

    public static class UnbindListener implements BaseLayoutHelper.LayoutViewUnBindListener {
        private Style mStyle;

        public UnbindListener(Style style) {
            this.mStyle = style;
        }

        @Override
        public void onUnbind(View layoutView, BaseLayoutHelper baseLayoutHelper) {
        }
    }


    @Nullable
    public LayoutHelper convertLayoutHelper(@Nullable LayoutHelper oldHelper) {
        return null;
    }


    private boolean mIsExposed = false;

    /**
     * Don't change card's mCells in binding process!
     * <p/>
     */
    public void onBindCell(int offset, int position, boolean showFromEnd) {
        if (!mIsExposed && serviceManager != null) {
            ExposureSupport exposureSupport = serviceManager.getService(ExposureSupport.class);
            if (exposureSupport != null) {
                mIsExposed = true;
                exposureSupport.onExposure(this, offset, position);
            }
        }
    }

    public BaseCell getCellById(String id) {
        for (int i = 0, size = mCells.size(); i < size; i++) {
            BaseCell target = mCells.get(i);
            if (target.id != null && target.id.equals(id)) {
                return target;
            }
        }
        return null;
    }

    public List<BaseCell> getCells() {
        return Collections.unmodifiableList(mCells);
    }

    @NonNull
    public Map<Range<Integer>, Card> getChildren() {
        return mChildren;
    }

    private final SparseBooleanArray pendingDeleteMap = new SparseBooleanArray();
    private final SparseArray<BaseCell> oldMap = new SparseArray<>();
    private final SparseArray<BaseCell> newMap = new SparseArray<>();

    public void setCells(@Nullable List<BaseCell> cells) {
        if (mPlaceholderCell != null)
            this.mCells.remove(mPlaceholderCell);

        oldMap.clear();
        pendingDeleteMap.clear();
        for (BaseCell cell : this.mCells) {
            oldMap.put(System.identityHashCode(cell), cell);
        }

        this.mCells.clear();
        if (cells != null) {
            for (BaseCell c : cells) {
                //noinspection unchecked
                this.addCellInternal(c, true);
            }
        }

        adjustPendingCells(true);

        newMap.clear();

        for (BaseCell cell : this.mCells) {
            newMap.put(System.identityHashCode(cell), cell);
        }

        for (int i = 0, size = oldMap.size(); i < size; i++) {
            int key = oldMap.keyAt(i);
            if (newMap.get(key) != null) {
                newMap.remove(key);
                pendingDeleteMap.put(key, true);
            }
        }

        for (int i = 0, size = pendingDeleteMap.size(); i < size; i++) {
            oldMap.remove(pendingDeleteMap.keyAt(i));
        }

        diffCells(newMap, oldMap);

        newMap.clear();
        oldMap.clear();
        pendingDeleteMap.clear();


        if (requirePlaceholderCell()) {
            this.mCells.add(mPlaceholderCell);
        }
    }


    public void addCell(@Nullable BaseCell cell) {
        addCellInternal(cell, false);

        adjustPendingCells(false);

        if (mPlaceholderCell != null && this.mCells.contains(mPlaceholderCell))
            this.mCells.remove(mPlaceholderCell);

        if (requirePlaceholderCell()) {
            this.mCells.add(mPlaceholderCell);
        }
    }

    public void addCells(@Nullable List<BaseCell> cells) {
        if (cells != null) {
            for (BaseCell cell : cells) {
                addCellInternal(cell, false);
            }
        }


        adjustPendingCells(false);

        if (mPlaceholderCell != null && this.mCells.contains(mPlaceholderCell))
            this.mCells.remove(mPlaceholderCell);

        if (requirePlaceholderCell()) {
            this.mCells.add(mPlaceholderCell);
        }
    }

    public void addCells(Card parent, int index, @Nullable List<BaseCell> cells) {
        if (cells != null) {
            int i = 0;
            for (BaseCell cell : cells) {
                addCellInternal(parent, index + i, cell, false);
                i++;
            }
        }


        adjustPendingCells(false);

        if (mPlaceholderCell != null && this.mCells.contains(mPlaceholderCell))
            this.mCells.remove(mPlaceholderCell);

        if (requirePlaceholderCell()) {
            this.mCells.add(mPlaceholderCell);
        }
    }

    public void removeAllCells() {
        for (int i = 0, size = mCells.size(); i < size; i++) {
            mCells.get(i).onRemoved();
        }
        mCells.clear();
    }

    public boolean removeCell(@Nullable BaseCell cell) {
        if (cell == null) {
            return false;
        }
        boolean removed = mCells.remove(cell);
        if (removed) {
            cell.onRemoved();
        }

        notifyDataChange();

        return removed;
    }

    public boolean removeCellSilently(@Nullable BaseCell cell) {
        if (cell == null) {
            return false;
        }
        boolean removed = mCells.remove(cell);
        if (removed) {
            cell.onRemoved();
        }
        return removed;
    }

    public boolean replaceCell(@Nullable BaseCell oldCell, @Nullable BaseCell newCell) {
        if (oldCell == null || newCell == null) {
            return false;
        }
        int index = mCells.indexOf(oldCell);
        if (index >= 0) {
            mCells.set(index, newCell);
            newCell.onAdded();
            oldCell.onRemoved();
            return true;
        } else {
            return false;
        }
    }

    private boolean addCellInternal(@Nullable BaseCell cell, boolean silent) {
        if (cell != null) {
            cell.parentId = id;
            cell.parent = this;
            cell.serviceManager = serviceManager;
            MVHelper mvHelper = getMVHelper();
            if (mvHelper != null) {
                if (mvHelper.isValid(cell, serviceManager)) {
                    if (cell.position >= 0 && !TextUtils.isEmpty(load)) {
                        cell.pos = cell.position;
                        mPendingCells.add(cell);
                        return true;
                    } else {
                        cell.pos = mHeader != null ? this.mCells.size() + 1 : this.mCells.size();
                    }

                    if (!silent && mIsActivated) {
                        // do cell added
                        cell.added();
                    }

                    this.mCells.add(cell);
                    if (mFooter != null) {
                        mFooter.pos = cell.pos + 1;
                    }
                    return true;
                }
            }
        }

        return false;
    }

    private boolean addCellInternal(Card parent, int index, @Nullable BaseCell cell, boolean silent) {
        if (cell != null) {
            cell.parentId = parent.id;
            cell.parent = parent;
            cell.serviceManager = serviceManager;
            MVHelper mvHelper = getMVHelper();
            if (mvHelper != null) {
                if (mvHelper.isValid(cell, serviceManager)) {
                    if (cell.position >= 0 && !TextUtils.isEmpty(load)) {
                        cell.pos = cell.position;
                        mPendingCells.add(cell);
                        return true;
                    } else {
                        //FixMe pos not correct when insert cell
                        cell.pos = mHeader != null ? this.mCells.size() + 1 : this.mCells.size();
                    }

                    if (!silent && mIsActivated) {
                        // do cell added
                        cell.added();
                    }

                    this.mCells.add(index, cell);
                    if (mFooter != null) {
                        mFooter.pos = cell.pos + 1;
                    }
                    return true;
                }
            }
        }

        return false;
    }


    private void adjustPendingCells(boolean silent) {
        if (mPendingCells.size() > 0) {
            Collections.sort(mPendingCells, CellPositionComparator.COMPARATOR);

            for (Iterator<BaseCell> iter = mPendingCells.iterator(); iter.hasNext(); ) {
                BaseCell next = iter.next();
                if (next.position < 0) continue;
                if (next.position < mCells.size()) {
                    mCells.add(next.position, next);
                    mInQueueCells.add(next);
                    iter.remove();

                    if (!silent)
                        next.added();
                } else {
                    break;
                }
            }
        }

        if (mInQueueCells.size() > 0) {
            // when do clean, the cell is already removed
            Collections.sort(mInQueueCells, CellPositionComparator.REVERSE_COMPARATOR);

            for (Iterator<BaseCell> iter = mInQueueCells.iterator(); iter.hasNext(); ) {
                BaseCell next = iter.next();
                if (next.position < 0) continue;
                if (next.position > mCells.size()) {
                    mPendingCells.add(next);
                    iter.remove();
                } else {
                    break;
                }
            }

        }

        if (TangramBuilder.isPrintLog()) {
            if (mPendingCells.size() > 0 && mInQueueCells.size() > 0) {
                Preconditions.checkState(mPendingCells.get(0).position >= mInQueueCells.get(mInQueueCells.size() - 1).position
                        , "Items in pendingQueue must have large position than Items in queue");
            }
        }
    }

    public void addChildCard(Card card) {

    }

    public void offsetChildCard(Card anchorCard, int offset) {

    }

    public void clearChildMap() {

    }

    public boolean isValid() {
        return (!TextUtils.isEmpty(stringType) || type >= 0) && serviceManager != null;
    }

    public final void notifyDataChange() {
        if (serviceManager instanceof Engine) {
            ((Engine) serviceManager).refresh();
        }
    }

    private void diffCells(@NonNull SparseArray<BaseCell> added, @NonNull SparseArray<BaseCell> removed) {
        if (!mIsActivated) return;

        for (int i = 0, size = added.size(); i < size; i++) {
            int key = added.keyAt(i);
            BaseCell cell = added.get(key);
            if (cell != null) {
                cell.added();
            }
        }


        for (int i = 0, size = removed.size(); i < size; i++) {
            int key = removed.keyAt(i);
            BaseCell cell = removed.get(key);
            if (cell != null) {
                cell.removed();
            }
        }
    }


    /*==========================================
     * Manage card lifecycle
     *==========================================*/
    protected void onAdded() {
        for (BaseCell cell : mCells) {
            cell.added();
        }
    }

    protected void onRemoved() {
        for (BaseCell cell : mCells) {
            cell.removed();
        }
    }

    protected void parseHeaderCell(@NonNull MVHelper resolver, @Nullable JSONObject header) {

    }

    protected void parseFooterCell(@NonNull MVHelper resolver, @Nullable JSONObject footer) {

    }


    /*==========================================
     * Place Holder
     *==========================================*/
    private BaseCell mPlaceholderCell;
    private float mTmpAspectRatio = Float.NaN;

    public void storeAspectRatio() {
        if (style != null && !Float.isNaN(style.aspectRatio)) {
            mTmpAspectRatio = style.aspectRatio;
            style.aspectRatio = Float.NaN;
        }
    }

    public void restoreAspectRatio() {
        if (style != null && !Float.isNaN(mTmpAspectRatio)) {
            style.aspectRatio = mTmpAspectRatio;
        }
    }

    public void enablePlaceholderView(View placeholderView, int placeholderHeight) {
        if (!TextUtils.isEmpty(load) && placeholderView != null) {
            storeAspectRatio();
            this.mPlaceholderCell = new PlaceholderCell(placeholderHeight, placeholderView);
            if (this.mCells.size() == 0) {
                mCells.add(mPlaceholderCell);
            }
        } else {
            this.mCells.remove(mPlaceholderCell);
            this.mPlaceholderCell = null;
        }
    }


    private boolean mPlaceholderRequired = true;

    public void showPlaceholderView(boolean shown) {
        this.mPlaceholderRequired = shown;
        if (!shown) {
            restoreAspectRatio();
        } else {
            storeAspectRatio();
        }
        if (!this.mCells.contains(mPlaceholderCell)) {
            if (requirePlaceholderCell()) {
                this.mCells.add(mPlaceholderCell);
                notifyDataChange();
            }
        } else {
            if (!requirePlaceholderCell() && this.mCells.remove(mPlaceholderCell))
                notifyDataChange();
        }


    }

    public BaseCell getPlaceholderCell() {
        return mPlaceholderCell;
    }

    public boolean requirePlaceholderCell() {
        return mPlaceholderRequired && mPlaceholderCell != null && !TextUtils.isEmpty(load)
                && (mCells.size() == 0 || (mCells.size() == 1 && mCells.contains(mPlaceholderCell)));
    }

    private MVHelper getMVHelper() {
        if (serviceManager != null) {
            return serviceManager.getService(MVHelper.class);
        }
        return null;
    }

    public Object optParam(String key) {
        if (extras.has(key))
            return extras.opt(key);
        if (style != null && style.extras != null)
            return style.extras.opt(key);
        return null;
    }

    public long optLongParam(String key) {
        if (extras.has(key))
            return extras.optLong(key);
        if (style != null && style.extras != null)
            return style.extras.optLong(key);
        return 0;
    }

    public int optIntParam(String key) {
        if (extras.has(key))
            return extras.optInt(key);
        if (style != null && style.extras != null)
            return style.extras.optInt(key);
        return 0;
    }

    public String optStringParam(String key) {
        if (extras.has(key))
            return extras.optString(key);
        if (style != null && style.extras != null)
            return style.extras.optString(key);
        return "";
    }

    public double optDoubleParam(String key) {
        if (extras.has(key))
            return extras.optDouble(key);
        if (style != null && style.extras != null)
            return style.extras.optDouble(key);
        return Double.NaN;
    }

    public boolean optBoolParam(String key) {
        if (extras.has(key))
            return extras.optBoolean(key);
        return style != null && style.extras != null && style.extras.optBoolean(key);
    }

    public JSONObject optJsonObjectParam(String key) {
        if (extras.has(key))
            return extras.optJSONObject(key);
        if (style != null && style.extras != null)
            return style.extras.optJSONObject(key);
        return null;
    }

    public JSONArray optJsonArrayParam(String key) {
        if (extras.has(key))
            return extras.optJSONArray(key);
        if (style != null && style.extras != null)
            return style.extras.optJSONArray(key);
        return null;
    }

    public Card findChildCardById(String id) {
        if (!mChildren.isEmpty()) {
            for (int i = 0, size = mChildren.size(); i < size; i++) {
                Card card = mChildren.get(i);
                if (card != null && card.id.equals(id)) {
                    return card;
                }
            }
        }
        return null;
    }

    public Map<Range<Integer>, Card> getChildrenCards() {
        return mChildren;
    }

    public static final class PlaceholderCell extends BaseCell {

        private int mHeight = 0;

        private View mPlaceholderView;

        private int mBgColor;

        public PlaceholderCell(int height, int bgColor) {
            this(height, null, bgColor);
        }

        public PlaceholderCell(int height, View placeholderView) {
            this(height, placeholderView, 0x0);
        }

        public PlaceholderCell(int height, View loadingView, int bgColor) {
            this.mHeight = height;
            this.mPlaceholderView = loadingView;
            this.mBgColor = bgColor;
            this.style = new Style();
            this.style.height = mHeight;
            this.style.bgColor = mBgColor;
            this.style.extras = new JSONObject();
            try {
                this.style.extras.put(Style.KEY_DISPLAY, Style.DISPLAY_BLOCK);
            } catch (JSONException e) {
                Log.w(TAG, Log.getStackTraceString(e), e);
            }

            this.type = TangramBuilder.TYPE_EXTENDED_VIEW;
            this.stringType = String.valueOf(TangramBuilder.TYPE_EXTENDED_VIEW);
        }

        public void bindView(@NonNull View view) {
            if (mPlaceholderView != null && view instanceof FrameLayout) {
                if (mPlaceholderView.getParent() instanceof FrameLayout) {
                    ((FrameLayout) mPlaceholderView.getParent()).removeView(mPlaceholderView);
                }

                ((FrameLayout) view).addView(mPlaceholderView);
            }
        }
    }

    private static class CellPositionComparator implements Comparator<BaseCell> {

        public static final CellPositionComparator COMPARATOR = new CellPositionComparator(false);
        public static final CellPositionComparator REVERSE_COMPARATOR = new CellPositionComparator(true);

        private int mLarge;
        private int mSmall;

        CellPositionComparator(boolean reverse) {
            mLarge = reverse ? -1 : 1;
            mSmall = -mLarge;
        }

        @Override
        public int compare(BaseCell lhs, BaseCell rhs) {


            if (lhs == null && rhs == null) return 0;
            if (lhs == null) return mSmall;
            if (rhs == null) return mLarge;

            return lhs.position < rhs.position ? mSmall : (lhs.position == rhs.position ? 0 : mLarge);

        }
    }

    public static final class NaNCard extends Card {

        @Override
        public boolean isValid() {
            return false;
        }
    }

}