package com.mm.android.deviceaddmodule.mobilecommon.widget.sticky.stickygridheaders;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.GridView;
import android.widget.ListAdapter;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * GridView that displays items in sections with headers that stick to the top of the view.
 *
 */
public class StickyGridHeadersGridView extends GridView implements OnScrollListener, OnItemClickListener,
        OnItemSelectedListener, OnItemLongClickListener {
    private static final String ERROR_PLATFORM = "Error supporting platform " + Build.VERSION.SDK_INT + ".";

    private static final int MATCHED_STICKIED_HEADER = -2;

    private static final int NO_MATCHED_HEADER = -1;

    protected static final int TOUCH_MODE_DONE_WAITING = 2;

    protected static final int TOUCH_MODE_DOWN = 0;

    protected static final int TOUCH_MODE_FINISHED_LONG_PRESS = -2;

    protected static final int TOUCH_MODE_REST = -1;

    protected static final int TOUCH_MODE_TAP = 1;

    static final String TAG = StickyGridHeadersGridView.class.getSimpleName();

    private static MotionEvent.PointerCoords[] getPointerCoords(MotionEvent e) {
        int n = e.getPointerCount();
        MotionEvent.PointerCoords[] r = new MotionEvent.PointerCoords[n];
        for (int i = 0; i < n; i++) {
            r[i] = new MotionEvent.PointerCoords();
            e.getPointerCoords(i, r[i]);
        }
        return r;
    }

    private static int[] getPointerIds(MotionEvent e) {
        int n = e.getPointerCount();
        int[] r = new int[n];
        for (int i = 0; i < n; i++) {
            r[i] = e.getPointerId(i);
        }
        return r;
    }

    public CheckForHeaderLongPress mPendingCheckForLongPress;

    public CheckForHeaderTap mPendingCheckForTap;

    private boolean mAreHeadersSticky = true;

    private final Rect mClippingRect = new Rect();

    private boolean mClippingToPadding;

    private boolean mClipToPaddingHasBeenSet;

    private int mColumnWidth;

    private long mCurrentHeaderId = -1;

    private DataSetObserver mDataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            reset();
        }

        @Override
        public void onInvalidated() {
            reset();
        }
    };

    private int mHeaderBottomPosition;

    private boolean mHeadersIgnorePadding;

    private int mHorizontalSpacing;

    private boolean mMaskStickxinzaieaderRegion = true;

    private float mMotionY;

    /**
     * Must be set from the wrapped GridView in the constructor.
     */
    private int mNumColumns;

    private boolean mNumColumnsSet;

    private int mNumMeasuredColumns = 1;

    private OnHeaderClickListener mOnHeaderClickListener;

    private OnHeaderLongClickListener mOnHeaderLongClickListener;

    private OnItemClickListener mOnItemClickListener;

    private OnItemLongClickListener mOnItemLongClickListener;

    private OnItemSelectedListener mOnItemSelectedListener;

    private PerformHeaderClick mPerformHeaderClick;

    private OnScrollListener mScrollListener;

    private int mScrollState = SCROLL_STATE_IDLE;

    private View mStickiedHeader;

    private Runnable mTouchModeReset;

    private int mTouchSlop;

    private int mVerticalSpacing;

    protected StickyGridHeadersBaseAdapterWrapper mAdapter;

    protected boolean mDataChanged;

    protected int mMotionHeaderPosition;

    protected int mTouchMode;

    boolean mHeaderChildBeingPressed = false;

    public StickyGridHeadersGridView(Context context) {
        this(context, null);
    }

    public StickyGridHeadersGridView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.gridViewStyle);
    }

    public StickyGridHeadersGridView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        super.setOnScrollListener(this);
        setVerticalFadingEdgeEnabled(false);

        if (!mNumColumnsSet) {
            mNumColumns = AUTO_FIT;
        }

        ViewConfiguration vc = ViewConfiguration.get(context);
        mTouchSlop = vc.getScaledTouchSlop();
    }

    public boolean areHeadersSticky() {
        return mAreHeadersSticky;
    }

    /**
     * Gets the header at an item position. However, the position must be that of a HeaderFiller.
     * 
     * @param position
     *            Position of HeaderFiller.
     * @return Header View wrapped in HeaderFiller or null if no header was found.
     */
    public View getHeaderAt(int position) {
        if (position == MATCHED_STICKIED_HEADER) {
            return mStickiedHeader;
        }

        try {
            return (View) getChildAt(position).getTag();
        } catch (Exception e) {
        }
        return null;
    }

    /**
     * Get the currently stickied header.
     * 
     * @return Current stickied header.
     */
    public View getStickiedHeader() {
        return mStickiedHeader;
    }

    public boolean getStickxinzaieaderIsTranscluent() {
        return !mMaskStickxinzaieaderRegion;
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        mOnItemClickListener.onItemClick(parent, view, mAdapter.translatePosition(position).mPosition, id);
    }

    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
        return mOnItemLongClickListener.onItemLongClick(parent, view, mAdapter.translatePosition(position).mPosition,
                id);
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        mOnItemSelectedListener.onItemSelected(parent, view, mAdapter.translatePosition(position).mPosition, id);
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        mOnItemSelectedListener.onNothingSelected(parent);
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;

        try {
            super.onRestoreInstanceState(ss.getSuperState());
        }catch (Exception e){
            e.printStackTrace();
        }
        mAreHeadersSticky = ss.areHeadersSticky;

        requestLayout();
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();

        SavedState ss = new SavedState(superState);
        ss.areHeadersSticky = mAreHeadersSticky;
        return ss;
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (mScrollListener != null) {
            mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
            scrollChanged(firstVisibleItem);
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (mScrollListener != null) {
            mScrollListener.onScrollStateChanged(view, scrollState);
        }

        mScrollState = scrollState;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        boolean wasHeaderChildBeingPressed = mHeaderChildBeingPressed;
        if (mHeaderChildBeingPressed) {
            final View tempHeader = getHeaderAt(mMotionHeaderPosition);
            final View headerHolder = mMotionHeaderPosition == MATCHED_STICKIED_HEADER ? tempHeader
                    : getChildAt(mMotionHeaderPosition);
            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                mHeaderChildBeingPressed = false;
            }
            if (tempHeader != null) {
                tempHeader.dispatchTouchEvent(transformEvent(ev, mMotionHeaderPosition));
                tempHeader.invalidate();
                tempHeader.postDelayed(new Runnable() {
                    public void run() {
                        invalidate(0, headerHolder.getTop(), getWidth(),
                                headerHolder.getTop() + headerHolder.getHeight());
                    }
                }, ViewConfiguration.getPressedStateDuration());
                invalidate(0, headerHolder.getTop(), getWidth(), headerHolder.getTop() + headerHolder.getHeight());
            }
        }

        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                if (mPendingCheckForTap == null) {
                    mPendingCheckForTap = new CheckForHeaderTap();
                }
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());

                final int y = (int) ev.getY();
                mMotionY = y;
                mMotionHeaderPosition = findMotionHeader(y);
                if (mMotionHeaderPosition == NO_MATCHED_HEADER || mScrollState == SCROLL_STATE_FLING) {
                    // Don't consume the event and pass it to super because we
                    // can't handle it yet.
                    break;
                } else {
                    View tempHeader = getHeaderAt(mMotionHeaderPosition);
                    if (tempHeader != null) {
                        if (tempHeader.dispatchTouchEvent(transformEvent(ev, mMotionHeaderPosition))) {
                            mHeaderChildBeingPressed = true;
                            tempHeader.setPressed(true);
                        }
                        tempHeader.invalidate();
                        if (mMotionHeaderPosition != MATCHED_STICKIED_HEADER) {
                            tempHeader = getChildAt(mMotionHeaderPosition);
                        }
                        invalidate(0, tempHeader.getTop(), getWidth(), tempHeader.getTop() + tempHeader.getHeight());
                    }
                }
                mTouchMode = TOUCH_MODE_DOWN;
                return true;
            case MotionEvent.ACTION_MOVE:
                if (mMotionHeaderPosition != NO_MATCHED_HEADER && Math.abs(ev.getY() - mMotionY) > mTouchSlop) {
                    // Detected scroll initiation so cancel touch completion on
                    // header.
                    mTouchMode = TOUCH_MODE_REST;
                    // if (!mHeaderChildBeingPressed) {
                    final View header = getHeaderAt(mMotionHeaderPosition);
                    if (header != null) {
                        header.setPressed(false);
                        header.invalidate();
                    }
                    final Handler handler = getHandler();
                    if (handler != null) {
                        handler.removeCallbacks(mPendingCheckForLongPress);
                    }
                    mMotionHeaderPosition = NO_MATCHED_HEADER;
                    // }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mTouchMode == TOUCH_MODE_FINISHED_LONG_PRESS) {
                    mTouchMode = TOUCH_MODE_REST;
                    return true;
                }
                if (mTouchMode == TOUCH_MODE_REST || mMotionHeaderPosition == NO_MATCHED_HEADER) {
                    break;
                }

                final View header = getHeaderAt(mMotionHeaderPosition);
                if (!wasHeaderChildBeingPressed) {
                    if (header != null) {
                        if (mTouchMode != TOUCH_MODE_DOWN) {
                            header.setPressed(false);
                        }

                        if (mPerformHeaderClick == null) {
                            mPerformHeaderClick = new PerformHeaderClick();
                        }

                        final PerformHeaderClick performHeaderClick = mPerformHeaderClick;
                        performHeaderClick.mClickMotionPosition = mMotionHeaderPosition;
                        performHeaderClick.rememberWindowAttachCount();

                        if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
                            final Handler handler = getHandler();
                            if (handler != null) {
                                handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap
                                        : mPendingCheckForLongPress);
                            }

                            if (!mDataChanged) {
                                /*
                                 * Got here so must be a tap. The long press would have triggered on
                                 * the callback handler.
                                 */
                                mTouchMode = TOUCH_MODE_TAP;
                                header.setPressed(true);
                                setPressed(true);
                                if (mTouchModeReset != null) {
                                    removeCallbacks(mTouchModeReset);
                                }
                                mTouchModeReset = new Runnable() {
                                    @Override
                                    public void run() {
                                        mMotionHeaderPosition = NO_MATCHED_HEADER;
                                        mTouchModeReset = null;
                                        mTouchMode = TOUCH_MODE_REST;
                                        header.setPressed(false);
                                        setPressed(false);
                                        header.invalidate();
                                        invalidate(0, header.getTop(), getWidth(), header.getHeight());
                                        if (!mDataChanged) {
                                            performHeaderClick.run();
                                        }
                                    }
                                };
                                postDelayed(mTouchModeReset, ViewConfiguration.getPressedStateDuration());
                            } else {
                                mTouchMode = TOUCH_MODE_REST;
                            }
                        } else if (!mDataChanged) {
                            performHeaderClick.run();
                        }
                    }
                }
                mTouchMode = TOUCH_MODE_REST;
                return true;
        }
        return super.onTouchEvent(ev);
    }

    public boolean performHeaderClick(View view, long id) {
        if (mOnHeaderClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            if (view != null) {
                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
            }
            mOnHeaderClickListener.onHeaderClick(this, view, id);
            return true;
        }

        return false;
    }

    public boolean performHeaderLongPress(View view, long id) {
        boolean handled = false;
        if (mOnHeaderLongClickListener != null) {
            handled = mOnHeaderLongClickListener.onHeaderLongClick(this, view, id);
        }

        if (handled) {
            if (view != null) {
                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
            }
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }

        return handled;
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        if (!mClipToPaddingHasBeenSet) {
            mClippingToPadding = true;
        }

        StickyGridHeadersBaseAdapter baseAdapter;
        if (adapter instanceof StickyGridHeadersBaseAdapter) {
            baseAdapter = (StickyGridHeadersBaseAdapter) adapter;
        } else if (adapter instanceof StickyGridHeadersSimpleAdapter) {
            // Wrap up simple adapter to auto-generate the data we need.
            baseAdapter = new StickyGridHeadersSimpleAdapterWrapper((StickyGridHeadersSimpleAdapter) adapter);
        } else {
            // Wrap up a list adapter so it is an adapter with zero headers.
            baseAdapter = new StickyGridHeadersListAdapterWrapper(adapter);
        }

        this.mAdapter = new StickyGridHeadersBaseAdapterWrapper(getContext(), this, baseAdapter);
        this.mAdapter.registerDataSetObserver(mDataSetObserver);
        reset();
        super.setAdapter(this.mAdapter);
    }

    public int translatePosition(int position) {
        return mAdapter.translatePosition(position).mPosition;
    }
    
    public void setAreHeadersSticky(boolean useStickxinzaieaders) {
        if (useStickxinzaieaders != mAreHeadersSticky) {
            mAreHeadersSticky = useStickxinzaieaders;
            requestLayout();
        }
    }

    @Override
    public void setClipToPadding(boolean clipToPadding) {
        super.setClipToPadding(clipToPadding);
        mClippingToPadding = clipToPadding;
        mClipToPaddingHasBeenSet = true;
    }

    @Override
    public void setColumnWidth(int columnWidth) {
        super.setColumnWidth(columnWidth);
        mColumnWidth = columnWidth;
    }

    /**
     * If set to true, headers will ignore horizontal padding.
     * 
     * @param b
     *            if true, horizontal padding is ignored by headers
     */
    public void setHeadersIgnorePadding(boolean b) {
        mHeadersIgnorePadding = b;
    }

    @Override
    public void setHorizontalSpacing(int horizontalSpacing) {
        super.setHorizontalSpacing(horizontalSpacing);
        mHorizontalSpacing = horizontalSpacing;
    }

    @Override
    public void setNumColumns(int numColumns) {
        super.setNumColumns(numColumns);
        mNumColumnsSet = true;
        this.mNumColumns = numColumns;
        if (numColumns != AUTO_FIT && mAdapter != null) {
            mAdapter.setNumColumns(numColumns);
        }
    }

    public void setOnHeaderClickListener(OnHeaderClickListener listener) {
        mOnHeaderClickListener = listener;
    }

    public void setOnHeaderLongClickListener(OnHeaderLongClickListener listener) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        mOnHeaderLongClickListener = listener;
    }

    @Override
    public void setOnItemClickListener(OnItemClickListener listener) {
        this.mOnItemClickListener = listener;
        super.setOnItemClickListener(this);
    }

    @Override
    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
        this.mOnItemLongClickListener = listener;
        super.setOnItemLongClickListener(this);
    }

    @Override
    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        this.mOnItemSelectedListener = listener;
        super.setOnItemSelectedListener(this);
    }

    @Override
    public void setOnScrollListener(OnScrollListener listener) {
        this.mScrollListener = listener;
    }

    public void setStickxinzaieaderIsTranscluent(boolean isTranscluent) {
        mMaskStickxinzaieaderRegion = !isTranscluent;
    }

    @Override
    public void setVerticalSpacing(int verticalSpacing) {
        super.setVerticalSpacing(verticalSpacing);
        mVerticalSpacing = verticalSpacing;
    }

    private int findMotionHeader(float y) {
        if (mStickiedHeader != null && y <= mHeaderBottomPosition) {
            return MATCHED_STICKIED_HEADER;
        }

        int vi = 0;
        for (int i = getFirstVisiblePosition(); i <= getLastVisiblePosition();) {
            long id = getItemIdAtPosition(i);
            if (id == StickyGridHeadersBaseAdapterWrapper.ID_HEADER) {
                View headerWrapper = getChildAt(vi);

                int bottom = headerWrapper.getBottom();
                int top = headerWrapper.getTop();
                if (y <= bottom && y >= top) {
                    return vi;
                }
            }
            i += mNumMeasuredColumns;
            vi += mNumMeasuredColumns;
        }

        return NO_MATCHED_HEADER;
    }

    private int getHeaderHeight() {
        if (mStickiedHeader != null) {
            return mStickiedHeader.getMeasuredHeight();
        }
        return 0;
    }

    private long headerViewPositionToId(int pos) {
        if (pos == MATCHED_STICKIED_HEADER) {
            return mCurrentHeaderId;
        }
        return mAdapter.getHeaderId(getFirstVisiblePosition() + pos);
    }

    private void measureHeader() {
        if (mStickiedHeader == null) {
            return;
        }

        int widthMeasureSpec;
        if (mHeadersIgnorePadding) {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
        } else {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth() - getPaddingLeft() - getPaddingRight(),
                    MeasureSpec.EXACTLY);
        }

        int heightMeasureSpec = 0;

        ViewGroup.LayoutParams params = mStickiedHeader.getLayoutParams();
        if (params != null && params.height > 0) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY);
        } else {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }
        mStickiedHeader.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        mStickiedHeader.measure(widthMeasureSpec, heightMeasureSpec);

        if (mHeadersIgnorePadding) {
            mStickiedHeader.layout(getLeft(), 0, getRight(), mStickiedHeader.getMeasuredHeight());
        } else {
            mStickiedHeader.layout(getLeft() + getPaddingLeft(), 0, getRight() - getPaddingRight(),
                    mStickiedHeader.getMeasuredHeight());
        }
    }

    private void reset() {
        mHeaderBottomPosition = 0;
        swapStickiedHeader(null);
        mCurrentHeaderId = INVALID_ROW_ID;
    }

    private void scrollChanged(int firstVisibleItem) {
        if (mAdapter == null || mAdapter.getCount() == 0 || !mAreHeadersSticky) {
            return;
        }

        View firstItem = getChildAt(0);
        if (firstItem == null) {
            return;
        }

        long newHeaderId;
        int selectedHeaderPosition = firstVisibleItem;

        int beforeRowPosition = firstVisibleItem - mNumMeasuredColumns;
        if (beforeRowPosition < 0) {
            beforeRowPosition = firstVisibleItem;
        }

        int secondRowPosition = firstVisibleItem + mNumMeasuredColumns;
        if (secondRowPosition >= mAdapter.getCount()) {
            secondRowPosition = firstVisibleItem;
        }

        if (mVerticalSpacing == 0) {
            newHeaderId = mAdapter.getHeaderId(firstVisibleItem);
        } else if (mVerticalSpacing < 0) {
            newHeaderId = mAdapter.getHeaderId(firstVisibleItem);
            View firstSecondRowView = getChildAt(mNumMeasuredColumns);
            if (firstSecondRowView.getTop() <= 0) {
                newHeaderId = mAdapter.getHeaderId(secondRowPosition);
                selectedHeaderPosition = secondRowPosition;
            } else {
                newHeaderId = mAdapter.getHeaderId(firstVisibleItem);
            }
        } else {
            int margin = getChildAt(0).getTop();
            if (0 < margin && margin < mVerticalSpacing) {
                newHeaderId = mAdapter.getHeaderId(beforeRowPosition);
                selectedHeaderPosition = beforeRowPosition;
            } else {
                newHeaderId = mAdapter.getHeaderId(firstVisibleItem);
            }
        }

        if (mCurrentHeaderId != newHeaderId) {
            swapStickiedHeader(mAdapter.getHeaderView(selectedHeaderPosition, mStickiedHeader, this));
            measureHeader();
            mCurrentHeaderId = newHeaderId;
        }

        final int childCount = getChildCount();
        if (childCount != 0) {
            View viewToWatch = null;
            int watchingChildDistance = 99999;

            // Find the next header after the stickied one.
            for (int i = 0; i < childCount; i += mNumMeasuredColumns) {
                View child = super.getChildAt(i);

                int childDistance;
                if (mClippingToPadding) {
                    childDistance = child.getTop() - getPaddingTop();
                } else {
                    childDistance = child.getTop();
                }

                if (childDistance < 0) {
                    continue;
                }

                if (mAdapter.getItemId(getPositionForView(child)) == StickyGridHeadersBaseAdapterWrapper.ID_HEADER
                        && childDistance < watchingChildDistance) {
                    viewToWatch = child;
                    watchingChildDistance = childDistance;
                }
            }

            int headerHeight = getHeaderHeight();

            // Work out where to draw stickied header using synchronised
            // scrolling.
            if (viewToWatch != null) {
                if (firstVisibleItem == 0 && super.getChildAt(0).getTop() > 0 && !mClippingToPadding) {
                    mHeaderBottomPosition = 0;
                } else {
                    if (mClippingToPadding) {
                        mHeaderBottomPosition = Math.min(viewToWatch.getTop(), headerHeight + getPaddingTop());
                        mHeaderBottomPosition = mHeaderBottomPosition < getPaddingTop() ? headerHeight
                                + getPaddingTop() : mHeaderBottomPosition;
                    } else {
                        mHeaderBottomPosition = Math.min(viewToWatch.getTop(), headerHeight);
                        mHeaderBottomPosition = mHeaderBottomPosition < 0 ? headerHeight : mHeaderBottomPosition;
                    }
                }
            } else {
                mHeaderBottomPosition = headerHeight;
                if (mClippingToPadding) {
                    mHeaderBottomPosition += getPaddingTop();
                }
            }
        }
    }

    private void swapStickiedHeader(View newStickiedHeader) {
        detachHeader(mStickiedHeader);
        attachHeader(newStickiedHeader);
        mStickiedHeader = newStickiedHeader;
    }

    private MotionEvent transformEvent(MotionEvent e, int headerPosition) {
        if (headerPosition == MATCHED_STICKIED_HEADER) {
            return e;
        }

        long downTime = e.getDownTime();
        long eventTime = e.getEventTime();
        int action = e.getAction();
        int pointerCount = e.getPointerCount();
        int[] pointerIds = getPointerIds(e);
        MotionEvent.PointerCoords[] pointerCoords = getPointerCoords(e);
        int metaState = e.getMetaState();
        float xPrecision = e.getXPrecision();
        float yPrecision = e.getYPrecision();
        int deviceId = e.getDeviceId();
        int edgeFlags = e.getEdgeFlags();
        int source = e.getSource();
        int flags = e.getFlags();

        View headerHolder = getChildAt(headerPosition);
        for (int i = 0; i < pointerCount; i++) {
            pointerCoords[i].y -= headerHolder.getTop();
        }
        MotionEvent n = MotionEvent.obtain(downTime, eventTime, action, pointerCount, pointerIds, pointerCoords,
                metaState, xPrecision, yPrecision, deviceId, edgeFlags, source, flags);
        return n;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
            scrollChanged(getFirstVisiblePosition());
        }

        boolean drawStickiedHeader = mStickiedHeader != null && mAreHeadersSticky
                && mStickiedHeader.getVisibility() == View.VISIBLE;
        int headerHeight = getHeaderHeight();
        int top = mHeaderBottomPosition - headerHeight;

        // Mask the region where we will draw the header later, but only if we
        // will draw a header and masking is requested.
        if (drawStickiedHeader && mMaskStickxinzaieaderRegion) {
            if (mHeadersIgnorePadding) {
                mClippingRect.left = 0;
                mClippingRect.right = getWidth();
            } else {
                mClippingRect.left = getPaddingLeft();
                mClippingRect.right = getWidth() - getPaddingRight();
            }
            mClippingRect.top = mHeaderBottomPosition;
            mClippingRect.bottom = getHeight();

            canvas.save();
            canvas.clipRect(mClippingRect);
        }

        // ...and draw the grid view.
        super.dispatchDraw(canvas);

        // Find headers.
        List<Integer> headerPositions = new ArrayList<>();
        int vi = 0;
        for (int i = getFirstVisiblePosition(); i <= getLastVisiblePosition();) {
            long id = getItemIdAtPosition(i);
            if (id == StickyGridHeadersBaseAdapterWrapper.ID_HEADER) {
                headerPositions.add(vi);
            }
            i += mNumMeasuredColumns;
            vi += mNumMeasuredColumns;
        }

        // Draw headers in list.
        for (int i = 0; i < headerPositions.size(); i++) {
            View frame = getChildAt(headerPositions.get(i));
            View header;
            try {
                header = (View) frame.getTag();
            } catch (Exception e) {
                return;
            }

            boolean headerIsStickied = ((StickyGridHeadersBaseAdapterWrapper.HeaderFillerView) frame).getHeaderId() == mCurrentHeaderId
                    && frame.getTop() < 0 && mAreHeadersSticky;
            if (header.getVisibility() != View.VISIBLE || headerIsStickied) {
                continue;
            }

            int widthMeasureSpec;
            if (mHeadersIgnorePadding) {
                widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
            } else {
                widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth() - getPaddingLeft() - getPaddingRight(),
                        MeasureSpec.EXACTLY);
            }

            int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            header.measure(MeasureSpec.makeMeasureSpec(0,  MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0,  MeasureSpec.UNSPECIFIED));
            header.measure(widthMeasureSpec, heightMeasureSpec);

            if (mHeadersIgnorePadding) {
                header.layout(getLeft(), 0, getRight(), frame.getHeight());
            } else {
                header.layout(getLeft() + getPaddingLeft(), 0, getRight() - getPaddingRight(), frame.getHeight());
            }

            if (mHeadersIgnorePadding) {
                mClippingRect.left = 0;
                mClippingRect.right = getWidth();
            } else {
                mClippingRect.left = getPaddingLeft();
                mClippingRect.right = getWidth() - getPaddingRight();
            }

            mClippingRect.bottom = frame.getBottom();
            mClippingRect.top = frame.getTop();
            canvas.save();
            canvas.clipRect(mClippingRect);
            if (mHeadersIgnorePadding) {
                canvas.translate(0, frame.getTop());
            } else {
                canvas.translate(getPaddingLeft(), frame.getTop());
            }
            header.draw(canvas);
            canvas.restore();
        }

        if (drawStickiedHeader && mMaskStickxinzaieaderRegion) {
            canvas.restore();
        } else if (!drawStickiedHeader) {
            // Done.
            return;
        }

        // Draw stickied header.
        int wantedWidth;
        if (mHeadersIgnorePadding) {
            wantedWidth = getWidth();
        } else {
            wantedWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        }
        if (mStickiedHeader.getWidth() != wantedWidth) {
            int widthMeasureSpec;
            if (mHeadersIgnorePadding) {
                widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
            } else {
                widthMeasureSpec = MeasureSpec.makeMeasureSpec(getWidth() - getPaddingLeft() - getPaddingRight(),
                        MeasureSpec.EXACTLY); // Bug here
            }
            int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            mStickiedHeader.measure(MeasureSpec.makeMeasureSpec(0,  MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0,  MeasureSpec.UNSPECIFIED));
            mStickiedHeader.measure(widthMeasureSpec, heightMeasureSpec);
            if (mHeadersIgnorePadding) {
                mStickiedHeader.layout(getLeft(), 0, getRight(), mStickiedHeader.getHeight());
            } else {
                mStickiedHeader.layout(getLeft() + getPaddingLeft(), 0, getRight() - getPaddingRight(),
                        mStickiedHeader.getHeight());
            }
        }

        if (mHeadersIgnorePadding) {
            mClippingRect.left = 0;
            mClippingRect.right = getWidth();
        } else {
            mClippingRect.left = getPaddingLeft();
            mClippingRect.right = getWidth() - getPaddingRight();
        }
        mClippingRect.bottom = top + headerHeight;
        if (mClippingToPadding) {
            mClippingRect.top = getPaddingTop();
        } else {
            mClippingRect.top = 0;
        }

        canvas.save();
        canvas.clipRect(mClippingRect);

        if (mHeadersIgnorePadding) {
            canvas.translate(0, top);
        } else {
            canvas.translate(getPaddingLeft(), top);
        }

        if (mHeaderBottomPosition != headerHeight) {
            canvas.saveLayerAlpha(0, 0, canvas.getWidth(), canvas.getHeight(), 255 * mHeaderBottomPosition
                    / headerHeight);
        }

        mStickiedHeader.draw(canvas);

        if (mHeaderBottomPosition != headerHeight) {
            canvas.restore();
        }
        canvas.restore();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mNumColumns == AUTO_FIT) {
            int numFittedColumns;
            if (mColumnWidth > 0) {
                int gridWidth = Math.max(MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(),
                        0);
                numFittedColumns = gridWidth / mColumnWidth;
                // Calculate measured columns accounting for requested grid
                // spacing.
                if (numFittedColumns > 0) {
                    while (numFittedColumns != 1) {
                        if (numFittedColumns * mColumnWidth + (numFittedColumns - 1) * mHorizontalSpacing > gridWidth) {
                            numFittedColumns--;
                        } else {
                            break;
                        }
                    }
                } else {
                    // Could not fit any columns in grid width, so default to a
                    // single column.
                    numFittedColumns = 1;
                }
            } else {
                // Mimic vanilla GridView behaviour where there is not enough
                // information to auto-fit columns.
                numFittedColumns = 2;
            }
            mNumMeasuredColumns = numFittedColumns;
        } else {
            // There were some number of columns requested so we will try to
            // fulfil the request.
            mNumMeasuredColumns = mNumColumns;
        }

        // Update adapter with number of columns.
        if (mAdapter != null) {
            mAdapter.setNumColumns(mNumMeasuredColumns);
        }

        measureHeader();

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    void attachHeader(View header) {
        if (header == null) {
            return;
        }

        try {
            Field attachInfoField = View.class.getDeclaredField("mAttachInfo");
            attachInfoField.setAccessible(true);
            Method method = View.class.getDeclaredMethod("dispatchAttachedToWindow",
                    Class.forName("android.view.View$AttachInfo"), Integer.TYPE);
            method.setAccessible(true);
            method.invoke(header, attachInfoField.get(this), View.GONE);
        } catch (NoSuchMethodException e) {
            throw new RuntimePlatformSupportException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimePlatformSupportException(e);
        } catch (IllegalArgumentException e) {
            throw new RuntimePlatformSupportException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimePlatformSupportException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimePlatformSupportException(e);
        } catch (NoSuchFieldException e) {
            throw new RuntimePlatformSupportException(e);
        }
    }

    void detachHeader(View header) {
        if (header == null) {
            return;
        }

        try {
            Method method = View.class.getDeclaredMethod("dispatchDetachedFromWindow");
            method.setAccessible(true);
            method.invoke(header);
        } catch (NoSuchMethodException e) {
            throw new RuntimePlatformSupportException(e);
        } catch (IllegalArgumentException e) {
            throw new RuntimePlatformSupportException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimePlatformSupportException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimePlatformSupportException(e);
        }
    }

    public interface OnHeaderClickListener {
        void onHeaderClick(AdapterView<?> parent, View view, long id);
    }

    public interface OnHeaderLongClickListener {
        boolean onHeaderLongClick(AdapterView<?> parent, View view, long id);
    }

    private class CheckForHeaderLongPress extends WindowRunnable implements Runnable {
        @Override
        public void run() {
            final View child = getHeaderAt(mMotionHeaderPosition);
            if (child != null) {
                final long longPressId = headerViewPositionToId(mMotionHeaderPosition);

                boolean handled = false;
                if (sameWindow() && !mDataChanged) {
                    handled = performHeaderLongPress(child, longPressId);
                }
                if (handled) {
                    mTouchMode = TOUCH_MODE_FINISHED_LONG_PRESS;
                    setPressed(false);
                    child.setPressed(false);
                } else {
                    mTouchMode = TOUCH_MODE_DONE_WAITING;
                }
            }
        }
    }

    private class PerformHeaderClick extends WindowRunnable implements Runnable {
        int mClickMotionPosition;

        @Override
        public void run() {
            // The data has changed since we posted this action to the event
            // queue, bail out before bad things happen.
            if (mDataChanged)
                return;

            if (mAdapter != null && mAdapter.getCount() > 0 && mClickMotionPosition != INVALID_POSITION
                    && mClickMotionPosition < mAdapter.getCount() && sameWindow()) {
                final View view = getHeaderAt(mClickMotionPosition);
                // If there is no view then something bad happened, the view
                // probably scrolled off the screen, and we should cancel the
                // click.
                if (view != null) {
                    performHeaderClick(view, headerViewPositionToId(mClickMotionPosition));
                }
            }
        }
    }

    /**
     * A base class for Runnables that will check that their view is still attached to the original
     * window as when the Runnable was created.
     */
    private class WindowRunnable {
        private int mOriginalAttachCount;

        public void rememberWindowAttachCount() {
            mOriginalAttachCount = getWindowAttachCount();
        }

        public boolean sameWindow() {
            return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
        }
    }

    final class CheckForHeaderTap implements Runnable {
        @Override
        public void run() {
            if (mTouchMode == TOUCH_MODE_DOWN) {
                mTouchMode = TOUCH_MODE_TAP;
                final View header = getHeaderAt(mMotionHeaderPosition);
                if (header != null && !mHeaderChildBeingPressed) {
                    if (!mDataChanged) {
                        header.setPressed(true);
                        setPressed(true);
                        refreshDrawableState();

                        final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
                        final boolean longClickable = isLongClickable();

                        if (longClickable) {
                            if (mPendingCheckForLongPress == null) {
                                mPendingCheckForLongPress = new CheckForHeaderLongPress();
                            }
                            mPendingCheckForLongPress.rememberWindowAttachCount();
                            postDelayed(mPendingCheckForLongPress, longPressTimeout);
                        } else {
                            mTouchMode = TOUCH_MODE_DONE_WAITING;
                        }
                    } else {
                        mTouchMode = TOUCH_MODE_DONE_WAITING;
                    }
                }
            }
        }
    }

    class RuntimePlatformSupportException extends RuntimeException {
        private static final long serialVersionUID = -6512098808936536538L;

        public RuntimePlatformSupportException(Exception e) {
            super(ERROR_PLATFORM, e);
        }
    }

    /**
     * Constructor called from {@link #CREATOR}
     */
    static class SavedState extends BaseSavedState {
        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };

        boolean areHeadersSticky;

        public SavedState(Parcelable superState) {
            super(superState);
        }

        /**
         * Constructor called from {@link #CREATOR}
         */
        private SavedState(Parcel in) {
            super(in);
            areHeadersSticky = in.readByte() != 0;
        }

        @Override
        public String toString() {
            return "StickyGridHeadersGridView.SavedState{" + Integer.toHexString(System.identityHashCode(this))
                    + " areHeadersSticky=" + areHeadersSticky + "}";
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeByte((byte) (areHeadersSticky ? 1 : 0));
        }
    }
}