/*
 * Copyright (C) 2012 www.amsoft.cn
 * 
 * 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.ab.view.carousel;

import java.util.ArrayList;
import java.util.Collections;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsSpinner;
import android.widget.SpinnerAdapter;

// TODO: Auto-generated Javadoc
/**
 * The Class CarouselSpinner.
 */
public abstract class CarouselSpinner extends CarouselAdapter<SpinnerAdapter> {

    /** The m adapter. */
    SpinnerAdapter mAdapter;

    /** The m height measure spec. */
    int mHeightMeasureSpec;
    
    /** The m width measure spec. */
    int mWidthMeasureSpec;
    
    /** The m block layout requests. */
    boolean mBlockLayoutRequests;

    /** The m selection left padding. */
    int mSelectionLeftPadding = 0;
    
    /** The m selection top padding. */
    int mSelectionTopPadding = 0;
    
    /** The m selection right padding. */
    int mSelectionRightPadding = 0;
    
    /** The m selection bottom padding. */
    int mSelectionBottomPadding = 0;
    
    /** The m spinner padding. */
    final Rect mSpinnerPadding = new Rect();

    /** The m recycler. */
    final RecycleBin mRecycler = new RecycleBin();
    
    /** The m data set observer. */
    private DataSetObserver mDataSetObserver;
		
    /**
     * Instantiates a new carousel spinner.
     *
     * @param context the context
     */
    public CarouselSpinner(Context context) {
        super(context);
        initCarouselSpinner();
    }

    /**
     * Instantiates a new carousel spinner.
     *
     * @param context the context
     * @param attrs the attrs
     */
    public CarouselSpinner(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * Instantiates a new carousel spinner.
     *
     * @param context the context
     * @param attrs the attrs
     * @param defStyle the def style
     */
    public CarouselSpinner(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initCarouselSpinner();
    }
    
    /**
     * Common code for different constructor flavors.
     */
    private void initCarouselSpinner() {
        setFocusable(true);
        setWillNotDraw(false);
    }    
           
    
	/**
	 * 描述：TODO.
	 *
	 * @version v1.0
	 * @return the adapter
	 * @see com.ab.view.carousel.CarouselAdapter#getAdapter()
	 * @author: amsoft.cn
	 * @date：2013-11-28 上午11:14:34
	 */
	public SpinnerAdapter getAdapter() {
        return mAdapter;
    }

	/**
	 * 描述：TODO.
	 *
	 * @version v1.0
	 * @param adapter the new adapter
	 * @see com.ab.view.carousel.CarouselAdapter#setAdapter(android.widget.Adapter)
	 * @author: amsoft.cn
	 * @date：2013-11-28 上午11:14:34
	 */
	public void setAdapter(SpinnerAdapter adapter) {
        if (null != mAdapter) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
            resetList();
        }
        
        mAdapter = adapter;
        
        mOldSelectedPosition = INVALID_POSITION;
        mOldSelectedRowId = INVALID_ROW_ID;
        
        if (mAdapter != null) {
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
            checkFocus();

            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            int position = mItemCount > 0 ? 0 : INVALID_POSITION;

            setSelectedPositionInt(position);
            setNextSelectedPositionInt(position);
            
            if (mItemCount == 0) {
                // Nothing selected
                checkSelectionChanged();
            }
            
        } else {
            checkFocus();            
            resetList();
            // Nothing selected
            checkSelectionChanged();
        }

        requestLayout();
		
	}

    /**
     * 描述：TODO.
     *
     * @version v1.0
     * @return the selected view
     * @see com.ab.view.carousel.CarouselAdapter#getSelectedView()
     * @author: amsoft.cn
     * @date：2013-11-28 上午11:14:34
     */
    public View getSelectedView() {
        if (mItemCount > 0 && mSelectedPosition >= 0) {
            return getChildAt(mSelectedPosition - mFirstPosition);
        } else {
            return null;
        }
    }
	
    /**
     * Jump directly to a specific item in the adapter data.
     *
     * @param position the position
     * @param animate the animate
     */
    public void setSelection(int position, boolean animate) {
        // Animate only if requested position is already on screen somewhere
        boolean shouldAnimate = animate && mFirstPosition <= position &&
                position <= mFirstPosition + getChildCount() - 1;
        setSelectionInt(position, shouldAnimate);
    }
    

    /**
     * Makes the item at the supplied position selected.
     * 
     * @param position Position to select
     * @param animate Should the transition be animated
     * 
     */
    void setSelectionInt(int position, boolean animate) {
        if (position != mOldSelectedPosition) {
            mBlockLayoutRequests = true;
            int delta  = position - mSelectedPosition;
            setNextSelectedPositionInt(position);
            layout(delta, animate);
            mBlockLayoutRequests = false;
        }
    }
    
    /**
     * Layout.
     *
     * @param delta the delta
     * @param animate the animate
     */
    abstract void layout(int delta, boolean animate);    

	/**
	 * 描述：TODO.
	 *
	 * @version v1.0
	 * @param position the new selection
	 * @see com.ab.view.carousel.CarouselAdapter#setSelection(int)
	 * @author: amsoft.cn
	 * @date：2013-11-28 上午11:14:34
	 */
	public void setSelection(int position) {
        setSelectionInt(position, false);
	}

    /**
     * Clear out all children from the list.
     */
    void resetList() {
        mDataChanged = false;
        mNeedSync = false;
        
        removeAllViewsInLayout();
        mOldSelectedPosition = INVALID_POSITION;
        mOldSelectedRowId = INVALID_ROW_ID;
        
        setSelectedPositionInt(INVALID_POSITION);
        setNextSelectedPositionInt(INVALID_POSITION);
        invalidate();
    }
	
    /**
     * On measure.
     *
     * @param widthMeasureSpec the width measure spec
     * @param heightMeasureSpec the height measure spec
     * @see android.view.View#measure(int, int)
     * 
     * Figure out the dimensions of this Spinner. The width comes from
     * the widthMeasureSpec as Spinnners can't have their width set to
     * UNSPECIFIED. The height is based on the height of the selected item
     * plus padding.
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize;
        int heightSize;

        mSpinnerPadding.left = getPaddingLeft() > mSelectionLeftPadding ? getPaddingLeft()
                : mSelectionLeftPadding;
        mSpinnerPadding.top = getPaddingTop() > mSelectionTopPadding ? getPaddingTop()
                : mSelectionTopPadding;
        mSpinnerPadding.right = getPaddingRight() > mSelectionRightPadding ? getPaddingRight()
                : mSelectionRightPadding;
        mSpinnerPadding.bottom = getPaddingBottom() > mSelectionBottomPadding ? getPaddingBottom()
                : mSelectionBottomPadding;

        if (mDataChanged) {
            handleDataChanged();
        }
        
        int preferredHeight = 0;
        int preferredWidth = 0;
        boolean needsMeasuring = true;
        
        int selectedPosition = getSelectedItemPosition();
        if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
            // Try looking in the recycler. (Maybe we were measured once already)
            View view = mRecycler.get(selectedPosition);
            if (view == null) {
                // Make a new one
                view = mAdapter.getView(selectedPosition, null, this);
            }

            if (view != null) {
                // Put in recycler for re-measuring and/or layout
                mRecycler.put(selectedPosition, view);
            }

            if (view != null) {
                if (view.getLayoutParams() == null) {
                    mBlockLayoutRequests = true;
                    view.setLayoutParams(generateDefaultLayoutParams());
                    mBlockLayoutRequests = false;
                }
                measureChild(view, widthMeasureSpec, heightMeasureSpec);
                
                preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
                preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
                
                needsMeasuring = false;
            }
        }
        
        if (needsMeasuring) {
            // No views -- just use padding
            preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
            if (widthMode == MeasureSpec.UNSPECIFIED) {
                preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
            }
        }

        preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
        preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());

        heightSize = resolveSize(preferredHeight, heightMeasureSpec);
        widthSize = resolveSize(preferredWidth, widthMeasureSpec);

        setMeasuredDimension(widthSize, heightSize);
        mHeightMeasureSpec = heightMeasureSpec;
        mWidthMeasureSpec = widthMeasureSpec;
    }
    
    /**
     * Gets the child height.
     *
     * @param child the child
     * @return the child height
     */
    int getChildHeight(View child) {
        return child.getMeasuredHeight();
    }
    
    /**
     * Gets the child width.
     *
     * @param child the child
     * @return the child width
     */
    int getChildWidth(View child) {
        return child.getMeasuredWidth();
    }
    
    /**
     * 描述：TODO.
     *
     * @version v1.0
     * @return the view group. layout params
     * @see android.view.ViewGroup#generateDefaultLayoutParams()
     * @author: amsoft.cn
     * @date：2013-11-28 上午11:14:34
     */
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        /*
         * Carousel expects Carousel.LayoutParams.
         */
        return new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    	
    }
    
    /**
     * Recycle all views.
     */
    void recycleAllViews() {
        final int childCount = getChildCount();
        final CarouselSpinner.RecycleBin recycleBin = mRecycler;
        final int position = mFirstPosition;

        // All views go in recycler
        for (int i = 0; i < childCount; i++) {
            View v = getChildAt(i);
            int index = position + i;
            recycleBin.put(index, v);
        }  
    }    
    
    /**
     * Override to prevent spamming ourselves with layout requests
     * as we place views.
     *
     * @see android.view.View#requestLayout()
     */
    @Override
    public void requestLayout() {
        if (!mBlockLayoutRequests) {
            super.requestLayout();
        }
    }
    

    /**
     * 描述：TODO.
     *
     * @version v1.0
     * @return the count
     * @see com.ab.view.carousel.CarouselAdapter#getCount()
     * @author: amsoft.cn
     * @date：2013-11-28 上午11:14:34
     */
    @Override
    public int getCount() {
        return mItemCount;
    }    
	
    /**
     * Maps a point to a position in the list.
     * 
     * @param x X in local coordinate
     * @param y Y in local coordinate
     * @return The position of the item which contains the specified point, or
     *         {@link #INVALID_POSITION} if the point does not intersect an item.
     */
    public int pointToPositionView(int x, int y) {    	

    	ArrayList<CarouselItemView> fitting = new ArrayList<CarouselItemView>();
    	
    	for(int i = 0; i < mAdapter.getCount(); i++){

    		CarouselItemView item = (CarouselItemView)getChildAt(i);

    		Matrix mm = item.getCIMatrix();
    		float[] pts = new float[3];
    		
    		pts[0] = item.getLeft();
    		pts[1] = item.getTop();
    		pts[2] = 0;
    		
    		mm.mapPoints(pts);
    		
    		int mappedLeft = (int)pts[0];
    		int mappedTop =  (int)pts[1];
    		    		
    		pts[0] = item.getRight();
    		pts[1] = item.getBottom();
    		pts[2] = 0;
    		
    		mm.mapPoints(pts);

    		int mappedRight = (int)pts[0];
    		int mappedBottom = (int)pts[1];
    		
    		if(mappedLeft < x && mappedRight > x & mappedTop < y && mappedBottom > y)
    			fitting.add(item);
    		
    	}
    	
    	Collections.sort(fitting);
    	
    	if(fitting.size() != 0)
    		return fitting.get(0).getIndex();
    	else
    		return mSelectedPosition;
    }
    
    /**
     * Maps a point to a position in the list.
     * 
     * @param x X in local coordinate
     * @param y Y in local coordinate
     * @return The position of the item which contains the specified point, or
     *         {@link #INVALID_POSITION} if the point does not intersect an item.
     */
    public int pointToPositionImage(int x, int y) {    	

    	ArrayList<CarouselItemImage> fitting = new ArrayList<CarouselItemImage>();
    	
    	for(int i = 0; i < mAdapter.getCount(); i++){

    		CarouselItemImage item = (CarouselItemImage)getChildAt(i);

    		Matrix mm = item.getCIMatrix();
    		float[] pts = new float[3];
    		
    		pts[0] = item.getLeft();
    		pts[1] = item.getTop();
    		pts[2] = 0;
    		
    		mm.mapPoints(pts);
    		
    		int mappedLeft = (int)pts[0];
    		int mappedTop =  (int)pts[1];
    		    		
    		pts[0] = item.getRight();
    		pts[1] = item.getBottom();
    		pts[2] = 0;
    		
    		mm.mapPoints(pts);

    		int mappedRight = (int)pts[0];
    		int mappedBottom = (int)pts[1];
    		
    		if(mappedLeft < x && mappedRight > x & mappedTop < y && mappedBottom > y)
    			fitting.add(item);
    		
    	}
    	
    	Collections.sort(fitting);
    	
    	if(fitting.size() != 0)
    		return fitting.get(0).getIndex();
    	else
    		return mSelectedPosition;
    }
    
    /**
     * The Class SavedState.
     */
    static class SavedState extends BaseSavedState {
        
        /** The selected id. */
        long selectedId;
        
        /** The position. */
        int position;

        /**
         * Constructor called from {@link AbsSpinner#onSaveInstanceState()}.
         *
         * @param superState the super state
         */
        SavedState(Parcelable superState) {
            super(superState);
        }
        
        /**
         * Constructor called from {@link #CREATOR}.
         *
         * @param in the in
         */
        private SavedState(Parcel in) {
            super(in);
            selectedId = in.readLong();
            position = in.readInt();
        }

        /**
         * 描述：TODO.
         *
         * @version v1.0
         * @param out the out
         * @param flags the flags
         * @see android.view.AbsSavedState#writeToParcel(android.os.Parcel, int)
         * @author: amsoft.cn
         * @date：2013-11-28 上午11:14:34
         */
        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeLong(selectedId);
            out.writeInt(position);
        }

        /**
         * 描述：TODO.
         *
         * @version v1.0
         * @return the string
         * @see java.lang.Object#toString()
         * @author: amsoft.cn
         * @date：2013-11-28 上午11:14:34
         */
        @Override
        public String toString() {
            return "AbsSpinner.SavedState{"
                    + Integer.toHexString(System.identityHashCode(this))
                    + " selectedId=" + selectedId
                    + " position=" + position + "}";
        }

        /** The Constant CREATOR. */
        public static final Parcelable.Creator<SavedState> CREATOR
                = new Parcelable.Creator<SavedState>() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

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

    /**
     * 描述：TODO.
     *
     * @version v1.0
     * @return the parcelable
     * @see android.view.View#onSaveInstanceState()
     * @author: amsoft.cn
     * @date：2013-11-28 上午11:14:34
     */
    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.selectedId = getSelectedItemId();
        if (ss.selectedId >= 0) {
            ss.position = getSelectedItemPosition();
        } else {
            ss.position = INVALID_POSITION;
        }
        return ss;
    }    

    /**
     * 描述：TODO.
     *
     * @version v1.0
     * @param state the state
     * @see android.view.View#onRestoreInstanceState(android.os.Parcelable)
     * @author: amsoft.cn
     * @date：2013-11-28 上午11:14:34
     */
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
  
        super.onRestoreInstanceState(ss.getSuperState());

        if (ss.selectedId >= 0) {
            mDataChanged = true;
            mNeedSync = true;
            mSyncRowId = ss.selectedId;
            mSyncPosition = ss.position;
            mSyncMode = SYNC_SELECTED_POSITION;
            requestLayout();
        }
    }
    
    /**
     * The Class RecycleBin.
     */
    class RecycleBin {
        
        /** The m scrap heap. */
        private final SparseArray<View> mScrapHeap = new SparseArray<View>();

        /**
         * Put.
         *
         * @param position the position
         * @param v the v
         */
        public void put(int position, View v) {
            mScrapHeap.put(position, v);
        }
        
        /**
         * Gets the.
         *
         * @param position the position
         * @return the view
         */
        View get(int position) {
            // System.out.print("Looking for " + position);
            View result = mScrapHeap.get(position);
            if (result != null) {
                // System.out.println(" HIT");
                mScrapHeap.delete(position);
            } else {
                // System.out.println(" MISS");
            }
            return result;
        }

        /**
         * Clear.
         */
        void clear() {
            final SparseArray<View> scrapHeap = mScrapHeap;
            final int count = scrapHeap.size();
            for (int i = 0; i < count; i++) {
                final View view = scrapHeap.valueAt(i);
                if (view != null) {
                    removeDetachedView(view, true);
                }
            }
            scrapHeap.clear();
        }
    }	
}
