/*
 * 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;

import android.graphics.Rect;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.alibaba.android.vlayout.layout.LayoutChunkResult;

import java.util.LinkedList;
import java.util.List;

import static com.alibaba.android.vlayout.VirtualLayoutManager.LayoutStateWrapper;

/**
 * Helper class to handle different layouts in {@link VirtualLayoutManager}
 *
 * @author villadora
 * @date 2015-8-14
 * @since 1.0.0
 */

public abstract class LayoutHelper {

    public static final Range<Integer> RANGE_ALL = Range.create(Integer.MIN_VALUE, Integer.MAX_VALUE);
    public static final Range<Integer> RANGE_EMPTY = Range.create(-1, -1);

    /**
     * Range for this layoutHelper, intialize with EMPTY
     */
    @NonNull
    Range<Integer> mRange = RANGE_EMPTY;

    int mZIndex = 0;


    /**
     * Is the position should be handle by this {@link LayoutHelper}
     *
     * @param position position without offset, which is the true index in {@link VirtualLayoutManager}
     * @return true if position in range returned by {@link #getRange()}
     */
    public boolean isOutOfRange(int position) {
        return !mRange.contains(position);
    }


    /**
     * Set range of items, which will be handled by this layoutHelper
     * start position must be greater than end position, otherwise {@link IllegalArgumentException}
     * will be thrown
     *
     * @param start start position of items handled by this layoutHelper
     * @param end   end position of items handled by this layoutHelper, if end < start, it will throw {@link IllegalArgumentException}
     * @throws MismatchChildCountException when the (start - end) doesn't equal to itemCount
     */
    public void setRange(int start, int end) {
        if (end < start) {
            throw new IllegalArgumentException("end should be larger or equeal then start position");
        }

        if (start == -1 && end == -1) {
            this.mRange = RANGE_EMPTY;
            onRangeChange(start, end);
            return;
        }

        if ((end - start + 1) != getItemCount()) {
            throw new MismatchChildCountException("ItemCount mismatch when range: " + mRange.toString() + " childCount: " + getItemCount());
        }

        if (start == mRange.getUpper() && end == mRange.getLower()) {
            // no change
            return;
        }

        this.mRange = Range.create(start, end);
        onRangeChange(start, end);
    }

    /**
     * This method will be called when range changes
     *
     * @param start start position of items handled by this layoutHelper
     * @param end   end position of items handled by this layoutHelper, if end < start, it will throw {@link IllegalArgumentException}
     */
    public void onRangeChange(final int start, final int end) {

    }

    /**
     * Return current range
     *
     * @return Range of integer
     */
    @NonNull
    public final Range<Integer> getRange() {
        return mRange;
    }


    /**
     * Given a chance to check and change the chosen anchorInfo
     *
     * @param state      current {@link }RecyclerView} 's state
     * @param anchorInfo the chosen anchorInfo
     * @param helper
     */
    public void checkAnchorInfo(RecyclerView.State state, VirtualLayoutManager.AnchorInfoWrapper anchorInfo, LayoutManagerHelper helper) {

    }

    /**
     * This method is called when scroll state is changed
     *
     * @param state         The new scroll state for RecyclerView
     * @param startPosition
     * @param endPosition
     */
    public void onScrollStateChanged(int state, int startPosition, int endPosition, LayoutManagerHelper helper) {

    }


    /**
     * Offset all child views attached to the parent RecyclerView by dx pixels along
     * the horizontal axis.
     *
     * @param dx Pixels to offset by
     */
    public void onOffsetChildrenHorizontal(int dx, LayoutManagerHelper helper) {

    }

    /**
     * Offset all child views attached to the parent RecyclerView by dy pixels along
     * the vertical axis.
     *
     * @param dy Pixels to offset by
     */
    public void onOffsetChildrenVertical(int dy, LayoutManagerHelper helper) {

    }

    /**
     * Get zIndex of this {@link LayoutHelper}
     *
     * @return zIndex of current layoutHelper
     */
    public int getZIndex() {
        return mZIndex;
    }

    /**
     * Experimental attribute, set zIndex of this {@link LayoutHelper},it does not mean the z-index of view. It just reorder the layoutHelpers in linear flow.
     * Do not use it currently.
     * @param zIndex
     */
    public void setZIndex(int zIndex) {
        this.mZIndex = zIndex;
    }

    /**
     * Get View that fixed in some position
     *
     * @return
     */
    @Nullable
    public View getFixedView() {
        return null;
    }


    @NonNull
    protected final List<View> mOffFlowViews = new LinkedList<>();

    /**
     * Get Views that out of normal flow layout
     *
     * @return list of views
     */
    @NonNull
    public List<View> getOffFlowViews() {
        return mOffFlowViews;
    }

    /**
     * Tell LayoutManager whether the child can be recycled, the recycleChild range is (startIndex, endIndex)
     *
     * @param childPos   recycled child index
     * @param startIndex start index of child will be recycled
     * @param endIndex   end index of child will be recycled
     * @param helper     a helper of type {@link LayoutManagerHelper}
     * @param fromStart  whether is recycleChildren from start
     * @return whether the child in <code>childPos</code> can be recycled
     */
    public boolean isRecyclable(int childPos, int startIndex, int endIndex, LayoutManagerHelper helper, boolean fromStart) {
        return true;
    }

    /**
     * Return children count
     *
     * @return the number of children
     */
    public abstract int getItemCount();

    /**
     * Set items' count
     *
     * @param itemCount how many children in this layoutHelper
     */
    public abstract void setItemCount(int itemCount);

    public abstract void doLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
                                  LayoutStateWrapper layoutState, LayoutChunkResult result,
                                  LayoutManagerHelper helper);

    public void onRefreshLayout(RecyclerView.State state, VirtualLayoutManager.AnchorInfoWrapper anchorInfo, LayoutManagerHelper helper) {

    }

    /**
     * Called before <code>doLayout</code>
     *
     * @param recycler recycler
     * @param state    RecyclerView's State
     * @param helper   LayoutManagerHelper to handle views
     */
    public abstract void beforeLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
                                      LayoutManagerHelper helper);

    /**
     * Called after <code>doLayout</code>
     *
     * @param recycler      recycler
     * @param state         RecyclerView's State
     * @param startPosition firstVisiblePosition in {@link RecyclerView}
     * @param endPosition   lastVisiblePosition in {@link RecyclerView}
     * @param scrolled      how many offset scrolled if layout is happened in a scrolling process
     * @param helper        LayoutManagerHelper
     */
    public abstract void afterLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
                                     int startPosition, int endPosition, int scrolled,
                                     LayoutManagerHelper helper);

    /**
     * Run to adjust layoutHelper's background area
     * @param startPosition
     * @param endPosition
     * @param helper
     */
    public abstract void adjustLayout(int startPosition, int endPosition, LayoutManagerHelper helper);

    public void onItemsChanged(LayoutManagerHelper helper) {

    }

    /**
     * Called when this layoutHelper will be removed from LayoutManager, please release views and other resources here
     *
     * @param helper LayoutManagerHelper
     */
    public abstract void clear(LayoutManagerHelper helper);

    /**
     * Whether a background layoutView is required
     *
     * @return true if require a layoutView
     */
    public abstract boolean requireLayoutView();

    /**
     * Bind properties to <code>layoutView</code>
     *
     * @param layoutView generated layoutView as backgroundView
     */
    public abstract void bindLayoutView(View layoutView);

    public abstract boolean isFixLayout();

    /**
     * Get margins between layout when layout child at <code>offset</code>
     * Or compute offset for align line during scrolling
     *
     * @param offset      anchor child's offset in current layoutHelper, for example, 0 means first item
     * @param isLayoutEnd is the layout process will do to end or start, true means it will lay views from start to end
     * @param useAnchor   whether offset is computed for scrolling or for anchor reset
     * @param helper      view layout helper
     * @return extra offset must be calculated in {@link VirtualLayoutManager}
     */
    public abstract int computeAlignOffset(int offset, boolean isLayoutEnd, boolean useAnchor,
        LayoutManagerHelper helper);

    public abstract int computeMarginStart(int offset, boolean isLayoutEnd, boolean useAnchor,
        LayoutManagerHelper helper);

    public abstract int computeMarginEnd(int offset, boolean isLayoutEnd, boolean useAnchor,
        LayoutManagerHelper helper);

    public abstract int computePaddingStart(int offset, boolean isLayoutEnd, boolean useAnchor,
        LayoutManagerHelper helper);

    public abstract int computePaddingEnd(int offset, boolean isLayoutEnd, boolean useAnchor,
        LayoutManagerHelper helper);

    public void onSaveState(final Bundle bundle) {

    }

    public void onRestoreInstanceState(final Bundle bundle) {

    }

}