/*
 * Copyright 2016 Vsevolod Ganin
 *
 * 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 net.ganin.darv;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Property;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

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

/**
 * RecyclerView adaptation for D-pad.
 */
public class DpadAwareRecyclerView extends RecyclerView implements
        ViewTreeObserver.OnGlobalFocusChangeListener {

    /**
     * Interface definition for a callback to be invoked when an item in this
     * DpadAwareRecyclerView has been clicked.
     */
    public interface OnItemClickListener {
        /**
         * Callback method to be invoked when an item in this DpadAwareRecyclerView has
         * been clicked.
         *
         * @param parent   the DpadAwareRecyclerView where the click happened
         * @param view     the view within the DpadAwareRecyclerView that was clicked (this
         *                 will be a view provided by the adapter)
         * @param position the position of the view in the adapter
         * @param id       the row id of the item that was clicked
         */
        void onItemClick(DpadAwareRecyclerView parent, View view, int position, long id);
    }

    /**
     * Interface definition for a callback to be invoked when
     * an item in this view has been selected.
     * <p>
     * Note that this interface differs from classic
     * {@link android.widget.AdapterView.OnItemSelectedListener}.
     */
    public interface OnItemSelectedListener {
        /**
         * Will be called when selector arrives at place.
         *
         * @param parent   The DpadAwareRecyclerView where the selection happened
         * @param view     The view within the DpadAwareRecyclerView that was selected
         * @param position The position of the view in the adapter
         * @param id       The row id of the item that is selected
         */
        void onItemSelected(DpadAwareRecyclerView parent, View view, int position, long id);

        /**
         * Will be called immediately after user issues controller command.
         *
         * @param parent   The DpadAwareRecyclerView where the selection happened
         * @param view     The view within the DpadAwareRecyclerView that was selected
         * @param position The position of the view in the adapter
         * @param id       The row id of the item that is selected
         */
        void onItemFocused(DpadAwareRecyclerView parent, View view, int position, long id);
    }

    private static final Property<Drawable, Rect> BOUNDS_PROP = Property.of(
            Drawable.class, Rect.class, "bounds");

    /**
     * Selector type.
     * <p>
     * Don't forget to change {@link #SELECTOR_COUNT} as well.
     */
    @IntDef({ FOREGROUND, BACKGROUND })
    @Retention(RetentionPolicy.SOURCE)
    private @interface Selector {}

    /*
       Selector types.
       Enumeration must start with 0 and be less than SELECTOR_COUNT.
       See enforceSelectorIndexBounds method.
     */
    private static final int FOREGROUND = 0;
    private static final int BACKGROUND = 1;
    private static final int SELECTOR_COUNT = 2;

    private class LocalAdapterDataObserver extends AdapterDataObserver {

        @Override
        public void onChanged() {
            // Case when adapter hasn't stable ids. Other case is handled natively by RecyclerView.
            if (!getAdapter().hasStableIds()) {
                mPendingSelectionInt = getSelectedItemPosition();
                if (mPendingSelectionInt == NO_POSITION) {
                    mPendingSelectionInt = 0;
                }
            }
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            // Case when adapter hasn't stable ids. Other case is handled natively by RecyclerView.
            if (!getAdapter().hasStableIds()) {
                int selectedPos = getSelectedItemPosition();
                if (selectedPos >= positionStart && selectedPos < positionStart + itemCount) {
                    mPendingSelectionInt = getSelectedItemPosition();
                }
                if (mPendingSelectionInt == NO_POSITION) {
                    mPendingSelectionInt = 0;
                }
            }
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            onItemRangeChanged(positionStart, itemCount);
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            int selectedPos = getSelectedItemPosition();
            if (selectedPos >= fromPosition && selectedPos < fromPosition + itemCount) {
                setSelection(selectedPos - fromPosition + toPosition);
            }
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            int selectedPos = getSelectedItemPosition();
            if (selectedPos >= positionStart && selectedPos < positionStart + itemCount) {
                setSelection(selectedPos + itemCount);
            }
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            int selectedPos = getSelectedItemPosition();
            if (selectedPos >= positionStart && selectedPos < positionStart + itemCount) {
                setSelection(positionStart);
            }
        }
    }

    private final class SelectAnimatorListener extends AnimatorListenerAdapter {

        @Nullable View mToSelect;
        @Nullable View mToDeselect;

        @Override
        public void onAnimationStart(Animator animation) {
            if (mToDeselect != null) {
                childSetSelected(mToDeselect, false);
            }
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            if (mToSelect != null) {
                childSetSelected(mToSelect, true);
            }
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            onAnimationEnd(animation);
        }
    }

    /**
     * Callback for {@link Drawable} selectors. View must keep this reference in order for
     * {@link java.lang.ref.WeakReference} in selectors to survive.
     *
     * @see Drawable#setCallback(Drawable.Callback)
     */
    private final Drawable.Callback mSelectorCallback = new Drawable.Callback() {
        @Override
        public void invalidateDrawable(@NonNull Drawable who) {
            invalidate(who.getBounds());
        }

        @Override
        public void scheduleDrawable(Drawable who, Runnable what, long when) {
            getHandler().postAtTime(what, who, when);
        }

        @Override
        public void unscheduleDrawable(Drawable who, Runnable what) {
            getHandler().removeCallbacks(what, who);
        }
    };

    private OnItemClickListener mOnItemClickListener;
    private OnItemSelectedListener mOnItemSelectedListener;

    private final AdapterDataObserver mDataObserver = new LocalAdapterDataObserver();

    /**
     * Adapter position that will be selected after certain layout pass.
     */
    private int mPendingSelectionInt = NO_POSITION;

    /**
     * Focus helper.
     */
    private final FocusArchivist mFocusArchivist = new FocusArchivist();

    private boolean mRememberLastFocus = true;

    private boolean mSmoothScrolling = false;

    /* Selector attributes */
    private final Rect mSelectorSourceRect = new Rect();
    private final Rect mSelectorDestRect = new Rect();
    private final Interpolator mTransitionInterpolator = new LinearInterpolator();
    private final Animator[] mSelectorAnimators = new Animator[SELECTOR_COUNT];
    private final Drawable[] mSelectorDrawables = new Drawable[SELECTOR_COUNT];
    private AnimatorSet mSelectorAnimator; // Unfortunately cannot be reused
    private int mSelectorVelocity = 0;
    /* Selector attributes */

    private final SelectAnimatorListener mReusableSelectListener = new SelectAnimatorListener();

    /**
     * {@inheritDoc}
     */
    public DpadAwareRecyclerView(@NonNull Context context) {
        super(context);
        init(context, null, 0);
    }

    /**
     * {@inheritDoc}
     */
    public DpadAwareRecyclerView(@NonNull Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
    }

    /**
     * {@inheritDoc}
     */
    public DpadAwareRecyclerView(@NonNull Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs, defStyle);
    }

    private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        if (attrs != null) {
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DpadAwareRecyclerView,
                    defStyle, 0);

            if (ta.hasValue(R.styleable.DpadAwareRecyclerView_backgroundSelector)) {
                setBackgroundSelector(ta.getDrawable(
                        R.styleable.DpadAwareRecyclerView_backgroundSelector));
            }

            if (ta.hasValue(R.styleable.DpadAwareRecyclerView_foregroundSelector)) {
                setForegroundSelector(ta.getDrawable(
                        R.styleable.DpadAwareRecyclerView_foregroundSelector));
            }

            if (ta.hasValue(R.styleable.DpadAwareRecyclerView_selectorVelocity)) {
                setSelectorVelocity(ta.getInt(
                        R.styleable.DpadAwareRecyclerView_selectorVelocity, 0));
            }

            setSmoothScrolling(ta.getBoolean(
                    R.styleable.DpadAwareRecyclerView_smoothScrolling, false));

            ta.recycle();
        }

        setFocusable(true);
        setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
        setWillNotDraw(false);
    }

    /**
     * Sets selectors velocity. Zero or less velocity means that transition will be instant.
     *
     * @param velocity velocity's values
     */
    public void setSelectorVelocity(int velocity) {
        mSelectorVelocity = velocity;
    }

    /**
     * Gets selectors velocity.
     *
     * @return selectors velocity
     */
    public int getSelectorVelocity() {
        return mSelectorVelocity;
    }

    /**
     * Sets smooth scrolling flag. If set to true, container will smoothly scroll to selected child
     * if it is outside of the viewport (by viewport one means some 'camera' rectangle, not
     * necessarily all screen).
     *
     * @param smoothScrolling if true, enable smooth scrolling
     */
    public void setSmoothScrolling(boolean smoothScrolling) {
        mSmoothScrolling = smoothScrolling;
    }

    /**
     * Gets smooth scrolling flag.
     *
     * @return true if smooth scrolling is enabled
     * @see #setSmoothScrolling
     */
    public boolean getSmoothScrolling() {
        return mSmoothScrolling;
    }

    /**
     * Sets background selector which will be drawn behind the child.
     *
     * @param drawable selector drawable
     */
    public void setBackgroundSelector(Drawable drawable) {
        setSelector(BACKGROUND, drawable);
    }

    /**
     * Sets background selector which will be drawn behind the child.
     *
     * @param resId selector drawable's resource ID
     */
    public void setBackgroundSelector(@DrawableRes int resId) {
        setBackgroundSelector(getDrawableResource(resId));
    }

    /**
     * Gets background selector which will be drawn behind the child.
     *
     * @return background selector
     */
    public Drawable getBackgroundSelector() {
        return getSelector(FOREGROUND);
    }

    /**
     * Sets foreground selector which will be drawn atop of the child.
     *
     * @param drawable selector drawable
     */
    public void setForegroundSelector(Drawable drawable) {
        setSelector(FOREGROUND, drawable);
    }

    /**
     * Sets foreground selector which will be drawn atop of the child.
     *
     * @param resId selector drawable's resource ID
     */
    public void setForegroundSelector(@DrawableRes int resId) {
        setForegroundSelector(getDrawableResource(resId));
    }

    /**
     * Gets foreground selector which will be drawn atop of the child.
     *
     * @return foreground selector
     */
    public Drawable getForegroundSelector() {
        return getSelector(FOREGROUND);
    }

    private void setSelector(@Selector int index, Drawable drawable) {
        enforceSelectorIndexBounds(index);

        mSelectorDrawables[index] = drawable;
        mSelectorAnimators[index] = createSelectorAnimator(drawable);
        setSelectorCallback(drawable);
    }

    private Drawable getSelector(int index) {
        enforceSelectorIndexBounds(index);

        return mSelectorDrawables[index];
    }

    /**
     * Ensures that passed number is valid selector index.
     *
     * @param index selector index
     * @throws IndexOutOfBoundsException if index is not valid selector index
     */
    private void enforceSelectorIndexBounds(int index) {
        if (index < 0 || index >= SELECTOR_COUNT) {
            throw new IndexOutOfBoundsException("Passed index is not in valid range which is"
                    + " [0; " + SELECTOR_COUNT + ").");
        }
    }

    /**
     * Register a callback to be invoked when an item in this RecyclerView has
     * been clicked.
     *
     * @param listener the callback that will be invoked
     */
    public void setOnItemClickListener(OnItemClickListener listener) {
        mOnItemClickListener = listener;
    }

    /**
     * @return the callback to be invoked with an item in this RecyclerView has
     *         been clicked, or null if no callback has been set
     */
    public OnItemClickListener getOnItemClickListener() {
        return mOnItemClickListener;
    }

    /**
     * Register a callback to be invoked when an item in this RecyclerView has
     * been selected.
     *
     * @param listener the callback that will run
     */
    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        mOnItemSelectedListener = listener;
    }

    /**
     * @return the callback to be invoked with an item in this RecyclerView has
     *         been selected, or null if no callback has been set
     */
    public OnItemSelectedListener getOnItemSelectedListener() {
        return mOnItemSelectedListener;
    }

    /**
     * Get adapter position of item that is currently focused/selected.
     *
     * @return selected item's adapter position
     */
    public int getSelectedItemPosition() {
        View focusedChild = getFocusedChild();
        return getChildAdapterPosition(focusedChild);
    }

    /**
     * Set adapter position for item to select if RecycleView currently has focus or schedule
     * selection on next focus obtainment.
     *
     * @param adapterPosition adapter position of item to be selected
     */
    public void setSelection(int adapterPosition) {
        scrollToPosition(adapterPosition);
        mPendingSelectionInt = adapterPosition;
    }

    /**
     * Get flag indicating that last focused view should be remembered in order to re-focus
     * it in future.
     *
     * @return true if focus remembering is enabled, otherwise disable
     */
    public boolean isRememberLastFocus() {
        return mRememberLastFocus;
    }

    /**
     * Set flag indicating that last focused view should be remembered in order to re-focus
     * it in future.
     *
     * @param rememberLastFocus true to enable focus remembering, otherwise disable
     */
    public void setRememberLastFocus(boolean rememberLastFocus) {
        mRememberLastFocus = rememberLastFocus;
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        setDescendantFocusability(enabled ? FOCUS_BEFORE_DESCENDANTS : FOCUS_BLOCK_DESCENDANTS);
        setFocusable(enabled);
    }

    @Override
    public void setAdapter(@Nullable Adapter newAdapter) {
        Adapter oldAdapter = getAdapter();
        if (oldAdapter != null) {
            oldAdapter.unregisterAdapterDataObserver(mDataObserver);
        }

        super.setAdapter(newAdapter);

        if (newAdapter != null) {
            newAdapter.registerAdapterDataObserver(mDataObserver);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        if (mPendingSelectionInt != NO_POSITION) {
            setSelectionOnLayout(mPendingSelectionInt);
            mPendingSelectionInt = NO_POSITION;
        }
    }

    private void setSelectionOnLayout(int position) {
        RecyclerView.ViewHolder holder = findViewHolderForAdapterPosition(position);

        if (holder != null) {
            if (hasFocus()) {
                holder.itemView.requestFocus();
            } else {
                mFocusArchivist.archiveFocus(this, holder.itemView);
            }
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        ViewTreeObserver obs = getViewTreeObserver();
        obs.addOnGlobalFocusChangeListener(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        ViewTreeObserver obs = getViewTreeObserver();
        obs.removeOnGlobalFocusChangeListener(this);
    }

    @Override
    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
        // FIXME: Parent view will get focus and immediately lose it in favor of some child.
        // So we actually can't enforce selectors visibility solely by placing this
        // in onFocusChanged(). Hence we handle it this way.
        enforceSelectorsVisibility(isInTouchMode(), hasFocus());
    }

    @Override
    protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);

        if (gainFocus) {
            // We favor natural focus if we don't want to remember focus AND if previously focused
            // rectangle is NOT null. Usually latter condition holds true if simple requestFocus()
            // is called and holds false if focus changed during navigation. Overall this is done
            // because of backward compatibility - some code is dependent on the fact that
            // focused position will be restored if requestFocus() is called and it expects
            // to have natural focus when ordinary navigation happens.
            boolean favorNaturalFocus = !mRememberLastFocus && previouslyFocusedRect != null;
            View lastFocusedView = mFocusArchivist.getLastFocus(this);
            if (favorNaturalFocus || lastFocusedView == null) {
                requestNaturalFocus(direction, previouslyFocusedRect);
            } else {
                lastFocusedView.requestFocus();
            }
        }
    }

    /**
     * Request natural focus.
     *
     * @param direction             direction in which focus is changing
     * @param previouslyFocusedRect previously focus rectangle
     */
    private void requestNaturalFocus(int direction, Rect previouslyFocusedRect) {
        FocusFinder ff = FocusFinder.getInstance();
        previouslyFocusedRect = previouslyFocusedRect == null
                ? new Rect(0, 0, 0, 0) : previouslyFocusedRect;
        View toFocus = ff.findNextFocusFromRect(this, previouslyFocusedRect, direction);
        toFocus = toFocus == null ? getChildAt(0) : toFocus;
        if (toFocus != null) {
            toFocus.requestFocus();
        }
    }

    @Override
    public void requestChildFocus(View child, @NonNull View focused) {
        super.requestChildFocus(child, focused);

        requestChildFocusInner(child, focused);
        fireOnItemFocusedEvent(child);
    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);

        if (state == SCROLL_STATE_IDLE) {
            View focusedChild = getFocusedChild();
            requestChildFocusInner(focusedChild, focusedChild);
        }
    }

    private void requestChildFocusInner(View child, @NonNull View focused) {
        // Try to find first non-null selector to take it as an anchor.
        Drawable refSelector = null;
        for (Drawable selector : mSelectorDrawables) {
            if (selector != null) {
                refSelector = selector;
                break;
            }
        }

        int scrollState = getScrollState();

        if (refSelector != null && scrollState == SCROLL_STATE_IDLE) {
            mSelectorSourceRect.set(refSelector.getBounds());

            // Focused cannot be null
            focused.getHitRect(mSelectorDestRect);

            mReusableSelectListener.mToSelect = child;
            mReusableSelectListener.mToDeselect = mFocusArchivist.getLastFocus(this);

            animateSelectorChange(mReusableSelectListener);

            mFocusArchivist.archiveFocus(this, child);
        }
    }

    @Override
    public void onDraw(@NonNull Canvas canvas) {
        drawSelectorIfVisible(BACKGROUND, canvas);

        super.onDraw(canvas);

        drawSelectorIfVisible(FOREGROUND, canvas);
    }

    private void drawSelectorIfVisible(@Selector int index, Canvas canvas) {
        enforceSelectorIndexBounds(index);

        Drawable selector = mSelectorDrawables[index];
        if (selector != null && selector.isVisible()) {
            selector.draw(canvas);
        }
    }

    /**
     * Animates selector when changes happen.
     */
    private void animateSelectorChange(Animator.AnimatorListener listener) {
        if (mSelectorAnimator != null) {
            mSelectorAnimator.cancel();
        }

        mSelectorAnimator = new AnimatorSet();

        for (int i = 0; i < SELECTOR_COUNT; i++) {
            mSelectorAnimator.playTogether(mSelectorAnimators[i]);
        }

        mSelectorAnimator.setInterpolator(mTransitionInterpolator);
        mSelectorAnimator.addListener(listener);

        int duration = 0;
        if (mSelectorVelocity > 0) {
            int dx = mSelectorDestRect.centerX() - mSelectorSourceRect.centerX();
            int dy = mSelectorDestRect.centerY() - mSelectorSourceRect.centerY();
            duration = computeTravelDuration(dx, dy, mSelectorVelocity);
        }

        mSelectorAnimator.setDuration(duration);
        mSelectorAnimator.start();
    }

    private Animator createSelectorAnimator(@Nullable Drawable selector) {
        return ObjectAnimator.ofObject(
                selector, BOUNDS_PROP, new RectEvaluator(),
                mSelectorSourceRect, mSelectorDestRect);
    }

    private int computeTravelDuration(int dx, int dy, int velocity) {
        return (int) (Math.sqrt(dx * dx + dy * dy) / velocity * 1000);
    }

    private void enforceSelectorsVisibility(boolean isInTouchMode, boolean hasFocus) {
        boolean visible = !isInTouchMode && hasFocus;

        for (Drawable selector : mSelectorDrawables) {
            if (selector != null) {
                selector.setVisible(visible, false);
            }
        }
    }

    @Nullable
    private Drawable getDrawableResource(int resId) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return getResources().getDrawable(resId, getContext().getTheme());
        } else {
            return getResources().getDrawable(resId);
        }
    }

    private void setSelectorCallback(@Nullable Drawable selector) {
        if (selector != null) {
            selector.setCallback(mSelectorCallback);
        }
    }

    private void childSetSelected(@NonNull View child, boolean selected) {
        child.setSelected(selected);

        if (selected) {
            fireOnItemSelectedEvent(child);
        }
    }

    @Override
    public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
        boolean consumed = super.dispatchKeyEvent(event);

        View focusedChild = getFocusedChild();

        if (focusedChild != null
                && mOnItemClickListener != null
                && event.getAction() == KeyEvent.ACTION_DOWN
                && isClickEvent(event)
                && event.getRepeatCount() == 0) {
            fireOnItemClickEvent(focusedChild);
        }

        return consumed;
    }

    private boolean isClickEvent(@NonNull KeyEvent event) {
        int keyCode = event.getKeyCode();
        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER;
    }

    @Override
    public void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) {
        super.addView(child, index, params);

        if (mOnItemClickListener != null) {
            child.setClickable(true);
        }
    }

    @Override
    public void getFocusedRect(Rect r) {
        getDrawingRect(r);
    }

    @Override
    public void addFocusables(@NonNull ArrayList<View> views, int direction, int focusableMode) {
        // Allow focus on children only if focus is already in this view
        if (hasFocus()) {
            super.addFocusables(views, direction, focusableMode);
        } else if (isFocusable()) {
            views.add(this);
        }
    }

    private void fireOnItemClickEvent(View child) {
        if (mOnItemClickListener != null) {
            int position = getChildAdapterPosition(child);
            long id = getChildItemId(child);
            mOnItemClickListener.onItemClick(this, child, position, id);
        }
    }

    private void fireOnItemFocusedEvent(View child) {
        if (mOnItemSelectedListener != null) {
            int position = getChildAdapterPosition(child);
            long id = getChildItemId(child);
            mOnItemSelectedListener.onItemFocused(this, child, position, id);
        }
    }

    private void fireOnItemSelectedEvent(View child) {
        if (mOnItemSelectedListener != null) {
            int position = getChildAdapterPosition(child);
            long id = getChildItemId(child);
            mOnItemSelectedListener.onItemSelected(this, child, position, id);
        }
    }
}