/*
 * Copyright 2016-2017 Davide Steduto
 *
 * 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 eu.davidea.flexibleadapter.helpers;

import android.graphics.Canvas;
import android.view.View;

import androidx.annotation.FloatRange;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.ItemTouchHelper.Callback;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemMoveListener;
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemSwipeListener;
import eu.davidea.flexibleadapter.utils.LayoutUtils;
import eu.davidea.viewholders.FlexibleViewHolder;

/**
 * This class is an implementation of {@link Callback} that enables Drag & Drop
 * and Swipe actions. Drag and Swipe events are started depending by the configuration.
 *
 * @author Davide Steduto
 * @since 23/01/2016 Created
 */
@SuppressWarnings({"WeakerAccess", "unused"})
public class ItemTouchHelperCallback extends Callback {

    protected static final float ALPHA_FULL = 1.0f;

    protected AdapterCallback mAdapterCallback;
    protected boolean longPressDragEnabled = false, handleDragEnabled = false, swipeEnabled = false;
    protected long mSwipeAnimationDuration = 300L, mDragAnimationDuration = 400L;
    protected float mSwipeThreshold = 0.5f, mMoveThreshold = 0.5f;
    protected int mSwipeFlags = -1;

    /*-------------*/
    /* CONSTRUCTOR */
    /*-------------*/

    public ItemTouchHelperCallback(AdapterCallback adapterCallback) {
        this.mAdapterCallback = adapterCallback;
    }

    /*-----------------------*/
    /* CONFIGURATION SETTERS */
    /*-----------------------*/
    /* DRAG */
    /*------*/

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return longPressDragEnabled;
    }

    /**
     * Enable / Disable the drag operation with long press on the ViewHolder.
     * <p>Default value is {@code false}.</p>
     *
     * @param isLongPressDragEnabled true to enable, false to disable
     */
    public void setLongPressDragEnabled(boolean isLongPressDragEnabled) {
        this.longPressDragEnabled = isLongPressDragEnabled;
    }

    /**
     * @return true if handle drag is enabled, false otherwise
     */
    public boolean isHandleDragEnabled() {
        return handleDragEnabled;
    }

    /**
     * Enable / Disable the drag of the itemView with a handle view.
     * <p>Default value is {@code false}.</p>
     *
     * @param handleDragEnabled true to activate, false to disable
     * @since 5.0.0-b1
     */
    public void setHandleDragEnabled(boolean handleDragEnabled) {
        this.handleDragEnabled = handleDragEnabled;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) {
        return true;
    }

    /**
     * Configures the fraction that the user should move the View to be considered as it is
     * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views
     * below it for a possible drop.
     * <p>Default value is {@code 0.5f}.</p>
     *
     * @param moveThreshold A float value that denotes the fraction of the View size.
     */
    public void setMoveThreshold(float moveThreshold) {
        this.mMoveThreshold = moveThreshold;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
        return mMoveThreshold;
    }

    /*-------*/
    /* SWIPE */
    /*-------*/

    /**
     * Enable the swipe operation on the ViewHolder.
     * <p>Default value is {@code false}.</p>
     *
     * @param isSwipeEnabled true to enable swipe, false to disable
     */
    public void setSwipeEnabled(boolean isSwipeEnabled) {
        this.swipeEnabled = isSwipeEnabled;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isItemViewSwipeEnabled() {
        return swipeEnabled;
    }

    /**
     * Configures the fraction that the user should move the View to be considered as swiped.
     * <p>Default value is {@code 0.5f}.</p>
     *
     * @param threshold A float value that denotes the fraction of the View size.
     */
    public void setSwipeThreshold(@FloatRange(from = 0.0, to = 1.0) float threshold) {
        this.mSwipeThreshold = threshold;
    }

    /**
     * Configures the directions in which the item can be swiped.
     * <p>Default values are {@link ItemTouchHelper#LEFT}, {@link ItemTouchHelper#RIGHT}
     * for VERTICAL LinearLayout.</p>
     * <b>NOTE:</b> Swipe is not supported in case of GridLayout and StaggeredGridLayout.
     *
     * @param swipeFlags flags directions, a combination of:
     *                   {@link ItemTouchHelper#LEFT}, {@link ItemTouchHelper#RIGHT},
     *                   {@link ItemTouchHelper#UP}, {@link ItemTouchHelper#DOWN}
     */
    public void setSwipeFlags(int swipeFlags) {
        this.mSwipeFlags = swipeFlags;
    }

    /**
     * Returns the fraction that the user should move the View to be considered as swiped.
     * The fraction is calculated with respect to RecyclerView's bounds.
     * <p>Default value is 0.5f, which means, to swipe a View, user must move the View at least
     * half of RecyclerView's width or height, depending on the swipe direction.</p>
     *
     * @param viewHolder The ViewHolder that is being dragged.
     * @return A float value that denotes the fraction of the View size.
     */
    @Override
    public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
        return mSwipeThreshold;
    }

    /**
     * Sets the animation duration of Drag event, starting when the items is released.
     * <p>Default value is {@code 400ms}.</p>
     *
     * @param animationDuration duration in milliseconds
     */
    public void setDragAnimationDuration(long animationDuration) {
        this.mDragAnimationDuration = animationDuration;
    }

    /**
     * Sets the animation duration of Swipe removal event, starting when the items is released.
     * <p>Default value is {@code 300ms}.</p>
     *
     * @param animationDuration duration in milliseconds
     */
    public void setSwipeAnimationDuration(long animationDuration) {
        this.mSwipeAnimationDuration = animationDuration;
    }

    /**
     * Provides the animation duration for Drag and Swipe previously set with
     * {@link #setDragAnimationDuration} and {@link #setSwipeAnimationDuration}.
     *
     * @return Drag or Swipe animation duration depending the animationType
     * @see #setDragAnimationDuration(long)
     * @see #setSwipeAnimationDuration(long)
     */
    @Override
    public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
        return animationType == ItemTouchHelper.ANIMATION_TYPE_DRAG ? mDragAnimationDuration : mSwipeAnimationDuration;
    }

    /*--------------*/
    /* MAIN METHODS */
    /*--------------*/

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        if (!mAdapterCallback.shouldMove(viewHolder.getAdapterPosition(), target.getAdapterPosition())) {
            return false;
        }
        // Notify the adapter of the move
        mAdapterCallback.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        // Notify the adapter of the swipe
        if (viewHolder instanceof ViewHolderCallback) {
            ViewHolderCallback viewHolderCallback = (ViewHolderCallback) viewHolder;
            if (viewHolderCallback.getFrontView().getTranslationX() != 0) {
                mAdapterCallback.onItemSwiped(viewHolder.getAdapterPosition(), direction);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        int dragFlags;
        int swipeFlags;
        // Set movement flags based on the Layout Manager and Orientation
        if (layoutManager instanceof GridLayoutManager || layoutManager instanceof StaggeredGridLayoutManager) {
            dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            swipeFlags = 0;
        } else if (LayoutUtils.getOrientation(recyclerView) == LinearLayoutManager.HORIZONTAL) {
            dragFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            swipeFlags = mSwipeFlags > 0 ? mSwipeFlags : ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        } else {
            dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            swipeFlags = mSwipeFlags > 0 ? mSwipeFlags : ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
        }
        // Disallow item swiping or dragging
        if (viewHolder instanceof ViewHolderCallback) {
            ViewHolderCallback viewHolderCallback = (ViewHolderCallback) viewHolder;
            if (!viewHolderCallback.isDraggable()) {
                dragFlags = 0;
            }
            if (!viewHolderCallback.isSwipeable()) {
                swipeFlags = 0;
            }
        }
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        // Notify the callback about the new event
        mAdapterCallback.onActionStateChanged(viewHolder, actionState);
        // We only want the active item to change
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            if (viewHolder instanceof ViewHolderCallback) {
                // Let the ViewHolder to know that this item is swiping or dragging
                ViewHolderCallback viewHolderCallback = (ViewHolderCallback) viewHolder;
                viewHolderCallback.onActionStateChanged(viewHolder.getAdapterPosition(), actionState);
                if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
                    getDefaultUIUtil().onSelected(viewHolderCallback.getFrontView());
                }
            }
        } else {
            super.onSelectedChanged(viewHolder, actionState);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        // Force full Alpha
        viewHolder.itemView.setAlpha(ALPHA_FULL);
        if (viewHolder instanceof ViewHolderCallback) {
            // Tell the view holder it's time to restore the idle state
            ViewHolderCallback viewHolderCallback = (ViewHolderCallback) viewHolder;
            getDefaultUIUtil().clearView(viewHolderCallback.getFrontView());
            // Hide Left or Right View
            setLayoutVisibility(viewHolderCallback, 0);
            viewHolderCallback.onItemReleased(viewHolder.getAdapterPosition());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
                            float dX, float dY, int actionState, boolean isCurrentlyActive) {
        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE &&
                viewHolder instanceof ViewHolderCallback) {

            // Update visibility for RearViews - Convert to custom VH
            ViewHolderCallback viewHolderCallback = (ViewHolderCallback) viewHolder;
            View frontView = viewHolderCallback.getFrontView();

            // Orientation independent
            float dragAmount = dX;
            if (dY != 0) {
                dragAmount = dY;
            }

            // Manage opening - Is Left or Right View?
            int swipingDirection = 0;//0 is to reset the frontView
            if (dragAmount > 0) {
                swipingDirection = ItemTouchHelper.RIGHT;//DOWN
            } else if (dragAmount < 0) {
                swipingDirection = ItemTouchHelper.LEFT;//TOP
            }

            setLayoutVisibility(viewHolderCallback, swipingDirection);
            // Translate the FrontView
            getDefaultUIUtil().onDraw(c, recyclerView, frontView, dX, dY, actionState, isCurrentlyActive);

        } else {
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
        }
    }

    private static void setLayoutVisibility(ViewHolderCallback viewHolderCallback, int swipeDirection) {
        if (viewHolderCallback.getRearRightView() != null) {
            viewHolderCallback.getRearRightView().setVisibility(
                    swipeDirection == ItemTouchHelper.LEFT ? View.VISIBLE : View.GONE);
        }
        if (viewHolderCallback.getRearLeftView() != null) {
            viewHolderCallback.getRearLeftView().setVisibility(
                    swipeDirection == ItemTouchHelper.RIGHT ? View.VISIBLE : View.GONE);
        }
    }

    /*------------------*/
    /* INNER INTERFACES */
    /*------------------*/

    /**
     * Internal interface for Adapter to listen for a move or swipe dismissal event
     * from a {@link ItemTouchHelperCallback}.
     *
     * @since 23/01/2016
     */
    public interface AdapterCallback {
        /**
         * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped
         * or when has been released.
         * <p>Override this method to receive touch events with its state.</p>
         *
         * @param viewHolder  the viewHolder touched
         * @param actionState one of {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
         *                    {@link ItemTouchHelper#ACTION_STATE_DRAG} or
         *                    {@link ItemTouchHelper#ACTION_STATE_IDLE}.
         */
        void onActionStateChanged(RecyclerView.ViewHolder viewHolder, int actionState);

        /**
         * Evaluate if positions are compatible for swapping.
         *
         * @param fromPosition the start position of the moving item
         * @param toPosition   the potential target position of the moving item
         * @return true if the from-item is allowed to swap with the to-item
         */
        boolean shouldMove(int fromPosition, int toPosition);

        /**
         * Called when an item has been dragged far enough to trigger a move. <b>This is called
         * every time an item is shifted</b>, and <strong>not</strong> at the end of a "drop" event.
         * <p>Implementations should call {@link Adapter#notifyItemMoved(int, int)}
         * after adjusting the underlying data to reflect this move.</p>
         *
         * @param fromPosition the start position of the moved item
         * @param toPosition   the resolved position of the moved item
         * @return true if the from-item has been swapped with the to-item
         */
        boolean onItemMove(int fromPosition, int toPosition);

        /**
         * Called when an item has been dismissed by a swipe.
         * <p>Implementations should decide to call or not {@link Adapter#notifyItemRemoved(int)}
         * after adjusting the underlying data to reflect this removal.</p>
         *
         * @param position  the position of the item dismissed
         * @param direction the direction to which the ViewHolder is swiped
         */
        void onItemSwiped(int position, int direction);
    }

    /**
     * Internal Interface for ViewHolder to notify of relevant callbacks from {@link Callback}.
     * <br/>This listener, is to intend as a further way to display How a ViewHolder will display
     * the middle and final activation state.
     * <p>Generally the final action should be handled by the listeners
     * {@link OnItemMoveListener} and {@link OnItemSwipeListener}.</p>
     *
     * @since 23/01/2016
     */
    public interface ViewHolderCallback {
        /**
         * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped.
         * <br/>Implementations should update the item view to indicate it's active state.
         * <p>{@link FlexibleViewHolder} class already provides an implementation to handle the
         * active state.</p>
         *
         * @param position    the position of the item touched
         * @param actionState one of {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
         *                    {@link ItemTouchHelper#ACTION_STATE_DRAG}.
         * @see FlexibleViewHolder#onActionStateChanged(int, int)
         */
        void onActionStateChanged(int position, int actionState);

        /**
         * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active
         * item state should be cleared.
         * <p>{@link FlexibleViewHolder} class already provides an implementation to disable the
         * active state.</p>
         *
         * @param position the position of the item released
         */
        void onItemReleased(int position);

        /**
         * @return true if the view is draggable, false otherwise
         */
        boolean isDraggable();

        /**
         * @return true if the view is swipeable, false otherwise
         */
        boolean isSwipeable();

        /**
         * On Swipe, override to return the Front View.
         * <p>Default is itemView.</p>
         *
         * @return the item Front View
         */
        View getFrontView();

        /**
         * On Swipe, override to return the Rear Left View.
         * <p>Default is null (no view).</p>
         *
         * @return the Rear Left View
         */
        View getRearLeftView();

        /**
         * On Swipe, override to return the Rear Right View.
         * <p>Default is null (no view).</p>
         *
         * @return the Rear Right View
         */
        View getRearRightView();
    }

}