content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.weex.ui.view;
  
  import android.annotation.SuppressLint;
  import android.content.Context;
  import android.graphics.Canvas;
  import android.graphics.Rect;
  import android.os.Handler;
  import android.os.Handler.Callback;
  import android.os.Message;
  import android.support.v4.view.NestedScrollingChild;
  import android.support.v4.view.NestedScrollingChildHelper;
  import android.support.v4.view.ViewCompat;
  import android.util.AttributeSet;
  import android.view.MotionEvent;
  import android.view.View;
  import android.widget.ScrollView;
  
  import org.apache.weex.common.WXThread;
  import org.apache.weex.ui.component.WXComponent;
  import org.apache.weex.ui.component.WXScroller;
  import org.apache.weex.ui.view.gesture.WXGesture;
  import org.apache.weex.ui.view.gesture.WXGestureObservable;
  import org.apache.weex.utils.WXLogUtils;
  import org.apache.weex.utils.WXReflectionUtils;
  
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  
  
  /**
   * Custom-defined scrollView
   */
  public class WXScrollView extends ScrollView implements Callback, IWXScroller,
          WXGestureObservable,NestedScrollingChild {
  
    private NestedScrollingChildHelper childHelper;
    private float ox;
    private float oy;
    private int[] consumed = new int[2];
    private int[] offsetInWindow = new int[2];
  
    int mScrollX;
    int mScrollY;
    private WXGesture wxGesture;
    private List<WXScrollViewListener> mScrollViewListeners;
    private WXScroller mWAScroller;
    //sticky
    private View mCurrentStickyView;
    private boolean mRedirectTouchToStickyView;
    private int mStickyOffset;
    private boolean mHasNotDoneActionDown = true;
    @SuppressLint("HandlerLeak")
    private Handler mScrollerTask;
    private int mInitialPosition;
    private int mCheckTime = 100;
    /**
     * The location of mCurrentStickyView
     */
    private int[] mStickyP = new int[2];
    /**
     * Location of the scrollView
     */
    private Rect mScrollRect;
    private int[] stickyScrollerP = new int[2];
    private int[] stickyViewP = new int[2];
    private boolean scrollable = true;
  
    public WXScrollView(Context context) {
      super(context);
      mScrollViewListeners = new ArrayList<>();
      init();
      try {
        WXReflectionUtils.setValue(this, "mMinimumVelocity", 5);
      } catch (Exception e) {
        WXLogUtils.e("[WXScrollView] WXScrollView: ", e);
      }
    }
  
    private void init() {
      setWillNotDraw(false);
      startScrollerTask();
      setOverScrollMode(View.OVER_SCROLL_NEVER);
      childHelper = new NestedScrollingChildHelper(this);
      childHelper.setNestedScrollingEnabled(true);
    }
  
    public void startScrollerTask() {
      if (mScrollerTask == null) {
        mScrollerTask = new Handler(WXThread.secure(this));
      }
      mInitialPosition = getScrollY();
      mScrollerTask.sendEmptyMessageDelayed(0, mCheckTime);
    }
  
    public WXScrollView(Context context, AttributeSet attrs) {
      super(context, attrs);
      init();
    }
  
    public WXScrollView(Context context, AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);
      setOverScrollMode(View.OVER_SCROLL_NEVER);
    }
  
    /**
     * Add listener for scrollView.
     */
    public void addScrollViewListener(WXScrollViewListener scrollViewListener) {
      if (!mScrollViewListeners.contains(scrollViewListener)) {
        mScrollViewListeners.add(scrollViewListener);
      }
    }
  
    public void removeScrollViewListener(WXScrollViewListener scrollViewListener) {
      mScrollViewListeners.remove(scrollViewListener);
    }
  
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
      if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        mRedirectTouchToStickyView = true;
      }
  
      if (mRedirectTouchToStickyView) {
        mRedirectTouchToStickyView = mCurrentStickyView != null;
  
        if (mRedirectTouchToStickyView) {
          mRedirectTouchToStickyView = ev.getY() <= mCurrentStickyView.getHeight()
                  && ev.getX() >= mCurrentStickyView.getLeft()
                  && ev.getX() <= mCurrentStickyView.getRight();
        }
      }
  
      if (mRedirectTouchToStickyView) {
        if (mScrollRect == null) {
          mScrollRect = new Rect();
          getGlobalVisibleRect(mScrollRect);
        }
        mCurrentStickyView.getLocationOnScreen(stickyViewP);
        ev.offsetLocation(0, stickyViewP[1] - mScrollRect.top);
      }
      boolean result = super.dispatchTouchEvent(ev);
      if (wxGesture != null) {
        result |= wxGesture.onTouch(this, ev);
      }
      return result;
    }
  
    @Override
    protected void dispatchDraw(Canvas canvas) {
      super.dispatchDraw(canvas);
      if (mCurrentStickyView != null) {
        canvas.save();
        mCurrentStickyView.getLocationOnScreen(mStickyP);
        int realOffset = (mStickyOffset <= 0 ? mStickyOffset : 0);
        canvas.translate(mStickyP[0], getScrollY() + realOffset);
        canvas.clipRect(0, realOffset, mCurrentStickyView.getWidth(),
                mCurrentStickyView.getHeight());
        mCurrentStickyView.draw(canvas);
        canvas.restore();
      }
    }
  
    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
      if(!scrollable) {
        return true; // when scrollable is set to false, then eat the touch event
      }
      if (mRedirectTouchToStickyView) {
  
        if (mScrollRect == null) {
          mScrollRect = new Rect();
          getGlobalVisibleRect(mScrollRect);
        }
        mCurrentStickyView.getLocationOnScreen(stickyViewP);
        ev.offsetLocation(0, -(stickyViewP[1] - mScrollRect.top));
      }
  
      if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        mHasNotDoneActionDown = false;
      }
  
      if (mHasNotDoneActionDown) {
        MotionEvent down = MotionEvent.obtain(ev);
        down.setAction(MotionEvent.ACTION_DOWN);
        mHasNotDoneActionDown = false;
        down.recycle();
      }
  
      if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        ox = ev.getX();
        oy = ev.getY();
        // Dispatch touch event to parent view
        startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL);
      }
  
      if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
        mHasNotDoneActionDown = true;
        // stop nested scrolling dispatch
        stopNestedScroll();
      }
  
      if (ev.getAction() == MotionEvent.ACTION_MOVE) {
        float clampedX = ev.getX();
        float clampedY = ev.getY();
        int dx = (int) (ox - clampedX);
        int dy = (int) (oy - clampedY);
  
        if (dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)) {
          // sub dx/dy was consumed by parent view!!!
          ev.setLocation(clampedX+consumed[0],clampedY+consumed[1]);
        }
        ox = ev.getX();
        oy = ev.getY();
      }
  
      return super.onTouchEvent(ev);
    }
  
    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
      childHelper.setNestedScrollingEnabled(enabled);
    }
  
    @Override
    public boolean isNestedScrollingEnabled() {
      return childHelper.isNestedScrollingEnabled();
    }
  
    @Override
    public boolean startNestedScroll(int axes) {
      return childHelper.startNestedScroll(axes);
    }
  
    @Override
    public void stopNestedScroll() {
      childHelper.stopNestedScroll();
    }
  
    @Override
    public boolean hasNestedScrollingParent() {
      return childHelper.hasNestedScrollingParent();
    }
  
    public boolean isScrollable() {
      return scrollable;
    }
  
    public void setScrollable(boolean scrollable) {
      this.scrollable = scrollable;
    }
  
    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
      return childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
    }
  
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
      return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }
  
    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
      return childHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }
  
    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
      return childHelper.dispatchNestedPreFling(velocityX, velocityY);
    }
  
    @Override
    public boolean onNestedPreFling(View target, float velocityX,
                                    float velocityY) {
      return dispatchNestedPreFling(velocityX, velocityY);
    }
  
    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY,
                                 boolean consumed) {
      return dispatchNestedFling(velocityX, velocityY, consumed);
    }
  
    @Override
    public void fling(int velocityY) {
      super.fling(velocityY);
      if (mScrollerTask != null) {
        mScrollerTask.removeMessages(0);
      }
      startScrollerTask();
    }
  
    @Override
    protected void onScrollChanged(int x, int y, int oldx, int oldy) {
      mScrollX = getScrollX();
      mScrollY = getScrollY();
      onScroll(WXScrollView.this, mScrollX, mScrollY);
      View view = getChildAt(getChildCount() - 1);
      if (view == null) {
        return;
      }
      int d = view.getBottom();
      d -= (getHeight() + mScrollY);
      if (d == 0) {
        onScrollToBottom(mScrollX, mScrollY);
      }
      int count = mScrollViewListeners == null ? 0 : mScrollViewListeners.size();
      for (int i = 0; i < count; ++i) {
        mScrollViewListeners.get(i).onScrollChanged(this, x, y, oldx, oldy);
      }
  
      showStickyView();
    }
  
    protected void onScroll(WXScrollView scrollView, int x, int y) {
      int count = mScrollViewListeners == null ? 0 : mScrollViewListeners.size();
      for (int i = 0; i < count; ++i) {
        mScrollViewListeners.get(i).onScroll(this, x, y);
      }
    }
  
    protected void onScrollToBottom(int x, int y) {
      int count = mScrollViewListeners == null ? 0 : mScrollViewListeners.size();
      for (int i = 0; i < count; ++i) {
        mScrollViewListeners.get(i).onScrollToBottom(this, x, y);
      }
    }
  
    private void showStickyView() {
      if(mWAScroller == null){
        return;
      }
      View curStickyView = procSticky(mWAScroller.getStickMap());
  
      if (curStickyView != null) {
        mCurrentStickyView = curStickyView;
      } else {
        mCurrentStickyView = null;
      }
    }
  
    private View procSticky(Map<String, Map<String, WXComponent>> mStickyMap) {
      if (mStickyMap == null) {
        return null;
      }
      Map<String, WXComponent> stickyMap = mStickyMap.get(mWAScroller.getRef());
      if (stickyMap == null) {
        return null;
      }
  
      Iterator<Entry<String, WXComponent>> iterator = stickyMap.entrySet().iterator();
      Entry<String, WXComponent> entry = null;
      WXComponent stickyData;
      while (iterator.hasNext()) {
        entry = iterator.next();
        stickyData = entry.getValue();
  
        getLocationOnScreen(stickyScrollerP);
        stickyData.getHostView().getLocationOnScreen(stickyViewP);
        int parentH = 0;
        if(stickyData.getParent()!=null && stickyData.getParent().getRealView()!=null){
          parentH=stickyData.getParent().getRealView().getHeight();
        }
        int stickyViewH = stickyData.getHostView().getHeight();
        int stickyShowPos = stickyScrollerP[1];
        int stickyStartHidePos = -parentH + stickyScrollerP[1] + stickyViewH;
        if (stickyViewP[1] <= stickyShowPos && stickyViewP[1] >= (stickyStartHidePos - stickyViewH)) {
          mStickyOffset = stickyViewP[1] - stickyStartHidePos;
          stickyData.setStickyOffset(stickyViewP[1]-stickyScrollerP[1]);
          return stickyData.getHostView();
        }else{
          stickyData.setStickyOffset(0);
        }
      }
      return null;
    }
  
    @Override
    public boolean handleMessage(Message msg) {
      switch (msg.what) {
        case 0:
          if (mScrollerTask != null) {
            mScrollerTask.removeMessages(0);
          }
          int newPosition = getScrollY();
          if (mInitialPosition - newPosition == 0) {//has stopped
            onScrollStopped(WXScrollView.this, getScrollX(), getScrollY());
          } else {
            onScroll(WXScrollView.this, getScrollX(), getScrollY());
            mInitialPosition = getScrollY();
            if (mScrollerTask != null) {
              mScrollerTask.sendEmptyMessageDelayed(0, mCheckTime);
            }
          }
          break;
        default:
          break;
      }
      return true;
    }
  
    protected void onScrollStopped(WXScrollView scrollView, int x, int y) {
      int count = mScrollViewListeners == null ? 0 : mScrollViewListeners.size();
      for (int i = 0; i < count; ++i) {
        mScrollViewListeners.get(i).onScrollStopped(this, x, y);
      }
    }
  
    @Override
    public void destroy() {
      if (mScrollerTask != null) {
        mScrollerTask.removeCallbacksAndMessages(null);
      }
    }
  
    @Override
    public void registerGestureListener(WXGesture wxGesture) {
      this.wxGesture = wxGesture;
    }
  
    @Override
    public WXGesture getGestureListener() {
      return wxGesture;
    }
  
    public Rect getContentFrame() {
      return new Rect(0, 0, computeHorizontalScrollRange(), computeVerticalScrollRange());
    }
  
    public interface WXScrollViewListener {
  
      void onScrollChanged(WXScrollView scrollView, int x, int y, int oldx, int oldy);
  
      void onScrollToBottom(WXScrollView scrollView, int x, int y);
  
      void onScrollStopped(WXScrollView scrollView, int x, int y);
  
      void onScroll(WXScrollView scrollView, int x, int y);
    }
  
    public void setWAScroller(WXScroller mWAScroller) {
      this.mWAScroller = mWAScroller;
    }
  }
  