/**
 * 
 */
package org.nativescript.widgets;

import org.nativescript.widgets.HorizontalScrollView.SavedState;
import android.content.Context;
import android.graphics.Rect;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ScrollView;

/**
 * @author hhristov
 *
 */
public class VerticalScrollView extends ScrollView {

	private final Rect mTempRect = new Rect();
	
    private int contentMeasuredWidth = 0;
    private int contentMeasuredHeight = 0;
    private int scrollableLength = 0;    
    private SavedState mSavedState;
    private boolean isFirstLayout = true;
	private boolean scrollEnabled = true;

    /**
     * True when the layout has changed but the traversal has not come through yet.
     * Ideally the view hierarchy would keep track of this for us.
     */
    private boolean mIsLayoutDirty = true;
    
    /**
     * The child to give focus to in the event that a child has requested focus while the
     * layout is dirty. This prevents the scroll from being wrong if the child has not been
     * laid out before requesting focus.
     */
    private View mChildToScrollTo = null;
    
	public VerticalScrollView(Context context) {
		super(context);
	}
	
    public int getScrollableLength() {
    	return this.scrollableLength;
    }

    public boolean getScrollEnabled() {
        return this.scrollEnabled;
    }

	public void setScrollEnabled(boolean value) {
		this.scrollEnabled = value;
	}

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // Do nothing with intercepted touch events if we are not scrollable
        if (!this.scrollEnabled) {
            return false;
        }

        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (!this.scrollEnabled && ev.getAction() == MotionEvent.ACTION_DOWN) {
            return false;
        }

        return super.onTouchEvent(ev);
    }

	@Override
	public void requestLayout() {
		this.mIsLayoutDirty = true;
		super.requestLayout();
	}

	@Override
	protected CommonLayoutParams generateDefaultLayoutParams() {
		return new CommonLayoutParams();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public CommonLayoutParams generateLayoutParams(AttributeSet attrs) {
		return new CommonLayoutParams();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
		return p instanceof CommonLayoutParams;
	}

	@Override
	protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams from) {
		if (from instanceof CommonLayoutParams)
			return new CommonLayoutParams((CommonLayoutParams)from);

		if (from instanceof FrameLayout.LayoutParams)
			return new CommonLayoutParams((FrameLayout.LayoutParams)from);

		if (from instanceof ViewGroup.MarginLayoutParams)
			return new CommonLayoutParams((ViewGroup.MarginLayoutParams)from);

		return new CommonLayoutParams(from);
	}

	@Override
	public void requestChildFocus(View child, View focused) {
	    if (!this.mIsLayoutDirty) {
	        this.scrollToChild(focused);
	    } 
	    else {
	        // The child may not be laid out yet, we can't compute the scroll yet
	    	this.mChildToScrollTo = focused;
	    }
	    super.requestChildFocus(child, focused);
	}
	    
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec);

        // Don't call measure because it will measure content twice.
		// ScrollView is expected to have single child so we measure only the first child.
	    View child = this.getChildCount() > 0 ? this.getChildAt(0) : null;
	    if (child == null) {
	        this.scrollableLength = 0;
	        this.contentMeasuredWidth = 0;
	        this.contentMeasuredHeight = 0;
	        this.setPadding(0, 0, 0, 0);
	    }
	    else {
	    	CommonLayoutParams.measureChild(child, widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
	    	this.contentMeasuredWidth = CommonLayoutParams.getDesiredWidth(child);
	    	this.contentMeasuredHeight = CommonLayoutParams.getDesiredHeight(child);

	    	// Android ScrollView does not account to child margins so we set them as paddings. Otherwise you can never scroll to bottom. 
	    	CommonLayoutParams lp = (CommonLayoutParams)child.getLayoutParams();
	    	this.setPadding(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin);
	    }
	
	    // Don't add in our paddings because they are already added as child margins. (we will include them twice if we add them).
	    // check the previous line - this.setPadding(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin);
//	    this.contentMeasuredWidth += this.getPaddingLeft() + this.getPaddingRight();
//	    this.contentMeasuredHeight += this.getPaddingTop() + this.getPaddingBottom();
	
	    // Check against our minimum height
	    this.contentMeasuredWidth = Math.max(this.contentMeasuredWidth, this.getSuggestedMinimumWidth());
	    this.contentMeasuredHeight = Math.max(this.contentMeasuredHeight, this.getSuggestedMinimumHeight());
	
	    int widthSizeAndState = resolveSizeAndState(this.contentMeasuredWidth, widthMeasureSpec, 0);
	    int heightSizeAndState = resolveSizeAndState(this.contentMeasuredHeight, heightMeasureSpec, 0);
	
	    this.setMeasuredDimension(widthSizeAndState, heightSizeAndState);
	}
	
	@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		int childHeight = 0;
		if (this.getChildCount() > 0) {
	    	View child = this.getChildAt(0);
	    	childHeight = child.getMeasuredHeight();
	    	
	    	int width = right - left;
	    	int height = bottom - top;
	
	    	this.scrollableLength = this.contentMeasuredHeight - height;
	    	CommonLayoutParams.layoutChild(child, 0, 0, width, Math.max(this.contentMeasuredHeight, height));
	    	this.scrollableLength = Math.max(0, this.scrollableLength);
		}
		
		this.mIsLayoutDirty = false;
	    // Give a child focus if it needs it
	    if (this.mChildToScrollTo != null && HorizontalScrollView.isViewDescendantOf(this.mChildToScrollTo, this)) {
	    	this.scrollToChild(this.mChildToScrollTo);
	    }
	    
	    this.mChildToScrollTo = null;
	    
	    int scrollX = this.getScrollX();
	    int scrollY = this.getScrollY();
	    if (this.isFirstLayout) {
	    	this.isFirstLayout = false;
	    	
	        final int scrollRange = Math.max(0, childHeight - (bottom - top - this.getPaddingTop() - this.getPaddingBottom()));
	        if (this.mSavedState != null) {
	        	scrollY = mSavedState.scrollPosition;
	            mSavedState = null;
	        }
	        
	        // Don't forget to clamp
	        if (scrollY > scrollRange) {
	        	scrollY = scrollRange;
	        } else if (scrollY < 0) {
	        	scrollY = 0;
	        }
	    }
	
	    // Calling this with the present values causes it to re-claim them
	    this.scrollTo(scrollX, scrollY);

		CommonLayoutParams.restoreOriginalParams(this);
	}

    @Override
    protected void onAttachedToWindow() {
    	super.onAttachedToWindow();
    	this.isFirstLayout = true;
    }
    
    @Override
    protected void onDetachedFromWindow() {
    	super.onDetachedFromWindow();
    	this.isFirstLayout = true;
    }
    
	@Override
	protected void onRestoreInstanceState(Parcelable state) {
	    SavedState ss = (SavedState) state;
	    super.onRestoreInstanceState(ss.getSuperState());
	    this.mSavedState = ss;
	    this.requestLayout();
	}
	
	@Override
	protected Parcelable onSaveInstanceState() {
	    Parcelable superState = super.onSaveInstanceState();
	    SavedState ss = new SavedState(superState);
	    ss.scrollPosition = this.getScrollY();
	    return ss;
	}
	
	private void scrollToChild(View child) {
	    child.getDrawingRect(mTempRect);
	
	    /* Offset from child's local coordinates to ScrollView coordinates */
	    offsetDescendantRectToMyCoords(child, mTempRect);
	
	    int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
	    if (scrollDelta != 0) {
	        this.scrollBy(scrollDelta, 0);
	    }
	}
}