/*
 * Copyright (C) 2015 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 com.android.setupwizardlib;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;

import androidx.annotation.IntDef;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * An {@link androidx.recyclerview.widget.RecyclerView.ItemDecoration} for RecyclerView to draw
 * dividers between items. This ItemDecoration will draw the drawable specified by
 * {@link #setDivider(android.graphics.drawable.Drawable)} as the divider in between each item by
 * default, and the behavior of whether the divider is shown can be customized by subclassing
 * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}.
 *
 * <p>Modified from v14 PreferenceFragment.DividerDecoration.
 */
public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    /* static section */

    /**
     * An interface to be implemented by a {@link RecyclerView.ViewHolder} which controls whether
     * dividers should be shown above and below that item.
     */
    public interface DividedViewHolder {

        /**
         * Returns whether divider is allowed above this item. A divider will be shown only if both
         * items immediately above and below it allows this divider.
         */
        boolean isDividerAllowedAbove();

        /**
         * Returns whether divider is allowed below this item. A divider will be shown only if both
         * items immediately above and below it allows this divider.
         */
        boolean isDividerAllowedBelow();
    }

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            DIVIDER_CONDITION_EITHER,
            DIVIDER_CONDITION_BOTH})
    public @interface DividerCondition {}

    public static final int DIVIDER_CONDITION_EITHER = 0;
    public static final int DIVIDER_CONDITION_BOTH = 1;

    /**
     * @deprecated Use {@link #DividerItemDecoration(android.content.Context)}
     */
    @Deprecated
    public static DividerItemDecoration getDefault(Context context) {
        return new DividerItemDecoration(context);
    }

    /* non-static section */

    private Drawable mDivider;
    private int mDividerHeight;
    private int mDividerIntrinsicHeight;
    @DividerCondition
    private int mDividerCondition;

    public DividerItemDecoration() {
    }

    public DividerItemDecoration(Context context) {
        final TypedArray a = context.obtainStyledAttributes(R.styleable.SuwDividerItemDecoration);
        final Drawable divider = a.getDrawable(
                R.styleable.SuwDividerItemDecoration_android_listDivider);
        final int dividerHeight = a.getDimensionPixelSize(
                R.styleable.SuwDividerItemDecoration_android_dividerHeight, 0);
        @DividerCondition final int dividerCondition = a.getInt(
                R.styleable.SuwDividerItemDecoration_suwDividerCondition,
                DIVIDER_CONDITION_EITHER);
        a.recycle();

        setDivider(divider);
        setDividerHeight(dividerHeight);
        setDividerCondition(dividerCondition);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (mDivider == null) {
            return;
        }
        final int childCount = parent.getChildCount();
        final int width = parent.getWidth();
        final int dividerHeight = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight;
        for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
            final View view = parent.getChildAt(childViewIndex);
            if (shouldDrawDividerBelow(view, parent)) {
                final int top = (int) ViewCompat.getY(view) + view.getHeight();
                mDivider.setBounds(0, top, width, top + dividerHeight);
                mDivider.draw(c);
            }
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
        if (shouldDrawDividerBelow(view, parent)) {
            outRect.bottom = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight;
        }
    }

    private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
        final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
        final int index = holder.getLayoutPosition();
        final int lastItemIndex = parent.getAdapter().getItemCount() - 1;
        if (isDividerAllowedBelow(holder)) {
            if (mDividerCondition == DIVIDER_CONDITION_EITHER) {
                // Draw the divider without consulting the next item if we only
                // need permission for either above or below.
                return true;
            }
        } else if (mDividerCondition == DIVIDER_CONDITION_BOTH || index == lastItemIndex) {
            // Don't draw if the current view holder doesn't allow drawing below
            // and the current theme requires permission for both the item below and above.
            // Also, if this is the last item, there is no item below to ask permission
            // for whether to draw a divider above, so don't draw it.
            return false;
        }
        // Require permission from index below to draw the divider.
        if (index < lastItemIndex) {
            final RecyclerView.ViewHolder nextHolder =
                    parent.findViewHolderForLayoutPosition(index + 1);
            if (!isDividerAllowedAbove(nextHolder)) {
                // Don't draw if the next view holder doesn't allow drawing above
                return false;
            }
        }
        return true;
    }

    /**
     * Whether a divider is allowed above the view holder. The allowed values will be combined
     * according to {@link #getDividerCondition()}. The default implementation delegates to
     * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows
     * the divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can
     * override this to give more information to decide whether a divider should be drawn.
     *
     * @return True if divider is allowed above this view holder.
     */
    protected boolean isDividerAllowedAbove(RecyclerView.ViewHolder viewHolder) {
        return !(viewHolder instanceof DividedViewHolder)
                || ((DividedViewHolder) viewHolder).isDividerAllowedAbove();
    }

    /**
     * Whether a divider is allowed below the view holder. The allowed values will be combined
     * according to {@link #getDividerCondition()}. The default implementation delegates to
     * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows
     * the divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can
     * override this to give more information to decide whether a divider should be drawn.
     *
     * @return True if divider is allowed below this view holder.
     */
    protected boolean isDividerAllowedBelow(RecyclerView.ViewHolder viewHolder) {
        return !(viewHolder instanceof DividedViewHolder)
                || ((DividedViewHolder) viewHolder).isDividerAllowedBelow();
    }

    /**
     * Sets the drawable to be used as the divider.
     */
    public void setDivider(Drawable divider) {
        if (divider != null) {
            mDividerIntrinsicHeight = divider.getIntrinsicHeight();
        } else {
            mDividerIntrinsicHeight = 0;
        }
        mDivider = divider;
    }

    /**
     * Gets the drawable currently used as the divider.
     */
    public Drawable getDivider() {
        return mDivider;
    }

    /**
     * Sets the divider height, in pixels.
     */
    public void setDividerHeight(int dividerHeight) {
        mDividerHeight = dividerHeight;
    }

    /**
     * Gets the divider height, in pixels.
     */
    public int getDividerHeight() {
        return mDividerHeight;
    }

    /**
     * Sets whether the divider needs permission from both the item view holder below
     * and above from where the divider would draw itself or just needs permission from
     * one or the other before drawing itself.
     */
    public void setDividerCondition(@DividerCondition int dividerCondition) {
        mDividerCondition = dividerCondition;
    }

    /**
     * Gets whether the divider needs permission from both the item view holder below
     * and above from where the divider would draw itself or just needs permission from
     * one or the other before drawing itself.
     */
    @DividerCondition
    public int getDividerCondition() {
        return mDividerCondition;
    }
}
