/*
 * 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.component;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.text.TextUtils;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;

import org.apache.weex.WXEnvironment;
import org.apache.weex.WXSDKInstance;
import org.apache.weex.WXSDKManager;
import org.apache.weex.annotation.Component;
import org.apache.weex.common.Constants;
import org.apache.weex.dom.WXEvent;
import org.apache.weex.ui.ComponentCreator;
import org.apache.weex.ui.action.BasicComponentData;
import org.apache.weex.ui.view.BaseFrameLayout;
import org.apache.weex.ui.view.WXCircleIndicator;
import org.apache.weex.ui.view.WXCirclePageAdapter;
import org.apache.weex.ui.view.WXCircleViewPager;
import org.apache.weex.ui.view.gesture.WXGestureType;
import org.apache.weex.utils.WXLogUtils;
import org.apache.weex.utils.WXUtils;
import org.apache.weex.utils.WXViewUtils;

import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

@Component(lazyload = false)

public class WXSlider extends WXVContainer<FrameLayout> {

  public static final String INDEX = "index";
  public static final String INFINITE = "infinite";

  private boolean isInfinite = true;

  Map<String, Object> params = new HashMap<>();
  private float offsetXAccuracy = 0.1f;
  private int initIndex = -1;
  private boolean keepIndex = false;
  private Runnable initRunnable;


  public static class Creator implements ComponentCreator {
    public WXComponent createInstance(WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) throws IllegalAccessException, InvocationTargetException, InstantiationException {
      return new WXSlider(instance, parent, basicComponentData);
    }
  }

  /**
   * Scrollable sliderview
   */
  /**
   * package
   **/
  WXCircleViewPager mViewPager;
  /**
   * Circle indicator
   */
  protected WXIndicator mIndicator;

  /**
   * Adapter for sliderview
   */
  protected WXCirclePageAdapter mAdapter;

  protected boolean mShowIndicators = true;

  protected OnPageChangeListener mPageChangeListener = new SliderPageChangeListener();

  @Deprecated
  public WXSlider(WXSDKInstance instance, WXVContainer parent, String instanceId, boolean isLazy, BasicComponentData basicComponentData) {
    this(instance, parent, basicComponentData);
  }

  public WXSlider(WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) {
    super(instance, parent, basicComponentData);
  }

  @Override
  protected BaseFrameLayout initComponentHostView(@NonNull Context context) {
    BaseFrameLayout view = new BaseFrameLayout(context);
    // init view pager
    if (getAttrs() != null) {
      Object obj = getAttrs().get(INFINITE);
      isInfinite = WXUtils.getBoolean(obj, true);
    }
    FrameLayout.LayoutParams pagerParams = new FrameLayout.LayoutParams(
        LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    mViewPager = new WXCircleViewPager(context);
    mViewPager.setCircle(isInfinite);
    mViewPager.setLayoutParams(pagerParams);

    // init adapter
    mAdapter = new WXCirclePageAdapter(isInfinite);
    mViewPager.setAdapter(mAdapter);
    // add to parent
    view.addView(mViewPager);
    mViewPager.addOnPageChangeListener(mPageChangeListener);

    registerActivityStateListener();

    return view;
  }

  /**
   * Slider is not a regular container,top/left/right/bottom not apply to view,expect indicator.
   */
  @Override
  public LayoutParams getChildLayoutParams(WXComponent child,View childView, int width, int height, int left, int right, int top, int bottom) {
    ViewGroup.LayoutParams lp = childView.getLayoutParams();
    if (lp == null) {
      lp = new FrameLayout.LayoutParams(width, height);
    } else {
      lp.width = width;
      lp.height = height;
    }

    if (lp instanceof ViewGroup.MarginLayoutParams) {
      //expect indicator .
      if (child instanceof WXIndicator) {
        this.setMarginsSupportRTL((ViewGroup.MarginLayoutParams) lp, left, top, right, bottom);
      } else {
        this.setMarginsSupportRTL((ViewGroup.MarginLayoutParams) lp, 0, 0, 0, 0);
      }
    }
    return lp;
  }


  @Override
  public void addEvent(String type) {
    super.addEvent(type);
    if (Constants.Event.SCROLL.equals(type)) {
      if (mViewPager == null) {
        return;
      }
      mViewPager.addOnPageChangeListener(new SliderOnScrollListener(this));
    }
  }

  @Override
  public boolean containsGesture(WXGestureType WXGestureType) {
    //Enable gesture for slider
    return super.containsGesture(WXGestureType);
  }

  @Override
  public ViewGroup getRealView() {
    return mViewPager;
  }

  @Override
  public void addSubView(View view, int index) {
    if (view == null || mAdapter == null) {
      return;
    }

    if (view instanceof WXCircleIndicator) {
      return;
    }
    mAdapter.addPageView(view);
    hackTwoItemsInfiniteScroll();
    if (initIndex != -1 && mAdapter.getRealCount() > initIndex) {
      if(initRunnable == null){
        initRunnable = new Runnable() {
          @Override
          public void run() {
            initIndex = getInitIndex();
            mViewPager.setCurrentItem(getRealIndex(initIndex));
            initIndex = -1;
            initRunnable = null;
          }
        };
      }
      mViewPager.removeCallbacks(initRunnable);
      mViewPager.postDelayed(initRunnable, 50);
    } else {
      if (!keepIndex) {
        mViewPager.setCurrentItem(getRealIndex(0));
      }
    }
    if (mIndicator != null) {
      mIndicator.getHostView().forceLayout();
      mIndicator.getHostView().requestLayout();
    }
  }

  @Override
  public void setLayout(WXComponent component) {
    if (mAdapter != null) {
      mAdapter.setLayoutDirectionRTL(this.isLayoutRTL());
    }
    super.setLayout(component);
  }

  @Override
  public void remove(WXComponent child, boolean destroy) {
    if (child == null || child.getHostView() == null || mAdapter == null) {
      return;
    }

    mAdapter.removePageView(child.getHostView());
    hackTwoItemsInfiniteScroll();
    super.remove(child,destroy);
  }

  @Override
  public void destroy() {
    super.destroy();
    if (mViewPager != null) {
      mViewPager.stopAutoScroll();
      mViewPager.removeAllViews();
      mViewPager.destory();
    }
  }

  @Override
  public void onActivityResume() {
    super.onActivityResume();
    if (mViewPager != null && mViewPager.isAutoScroll()) {
      mViewPager.startAutoScroll();
    }
  }

  @Override
  public void onActivityStop() {
    super.onActivityStop();
    if (mViewPager != null) {
      mViewPager.pauseAutoScroll();
    }
  }

  public void addIndicator(WXIndicator indicator) {
    FrameLayout root = getHostView();
    if (root == null) {
      return;
    }
    mIndicator = indicator;
    mIndicator.setShowIndicators(mShowIndicators);
    WXCircleIndicator indicatorView = indicator.getHostView();
    if (indicatorView != null) {
      indicatorView.setCircleViewPager(mViewPager);
      // indicatorView.setOnPageChangeListener(mPageChangeListener);  // commented for twice onChange() called when do slide.
      root.addView(indicatorView);
    }

  }


  private int getInitIndex(){
    Object index = getAttrs().get(Constants.Name.INDEX);
    int select = WXUtils.getInteger(index, initIndex);
    if(mAdapter == null || mAdapter.getCount() == 0){
      return  0;
    }
    if(select >= mAdapter.getRealCount()){
      select = select%mAdapter.getRealCount();
    }

    return select;
  }

  private int getRealIndex(int idx) {
    int retIdx = idx;

    if (mAdapter.getRealCount() > 0) {
      if(idx >= mAdapter.getRealCount()) retIdx = mAdapter.getRealCount() - 1;
      if (isLayoutRTL()) {
        retIdx = mAdapter.getRealCount() - 1 - retIdx;
      }
    }
    retIdx = retIdx + 0;
    return retIdx;
  }

  @Override
  protected boolean setProperty(String key, Object param) {
    switch (key) {
      case Constants.Name.VALUE:
        String value = WXUtils.getString(param, null);
        if (value != null) {
          setValue(value);
        }
        return true;
      case Constants.Name.AUTO_PLAY:
        String aotu_play = WXUtils.getString(param, null);
        if (aotu_play != null) {
          setAutoPlay(aotu_play);
        }
        return true;
      case Constants.Name.SHOW_INDICATORS:
        String indicators = WXUtils.getString(param, null);
        if (indicators != null) {
          setShowIndicators(indicators);
        }
        return true;
      case Constants.Name.INTERVAL:
        Integer interval = WXUtils.getInteger(param, null);
        if (interval != null) {
          setInterval(interval);
        }
        return true;
      case Constants.Name.INDEX:
        Integer index = WXUtils.getInteger(param, null);
        if (index != null) {
          setIndex(index);
        }
        return true;
      case Constants.Name.OFFSET_X_ACCURACY:
        Float accuracy = WXUtils.getFloat(param, 0.1f);
        if (accuracy != 0) {
          setOffsetXAccuracy(accuracy);
        }
        return true;
      case Constants.Name.SCROLLABLE:
        boolean scrollable = WXUtils.getBoolean(param, true);
        setScrollable(scrollable);
        return true;
      case Constants.Name.KEEP_INDEX:
        this.keepIndex = WXUtils.getBoolean(param, false);
        return true;
    }
    return super.setProperty(key, param);
  }

  @Deprecated
  @WXComponentProp(name = Constants.Name.VALUE)
  public void setValue(String value) {
    if (value == null || getHostView() == null) {
      return;
    }
    int i;
    try {
      i = Integer.parseInt(value);
    } catch (NumberFormatException e) {
      WXLogUtils.e("", e);
      return;
    }

    setIndex(i);
  }

  @WXComponentProp(name = Constants.Name.AUTO_PLAY)
  public void setAutoPlay(String autoPlay) {
    if (TextUtils.isEmpty(autoPlay) || autoPlay.equals("false")) {
      mViewPager.stopAutoScroll();
    } else {
      mViewPager.stopAutoScroll();
      mViewPager.startAutoScroll();
    }
  }

  @WXComponentProp(name = Constants.Name.SHOW_INDICATORS)
  public void setShowIndicators(String show) {
    if (TextUtils.isEmpty(show) || show.equals("false")) {
      mShowIndicators = false;
    } else {
      mShowIndicators = true;
    }

    if (mIndicator == null) {
      return;
    }
    mIndicator.setShowIndicators(mShowIndicators);
  }

  @WXComponentProp(name = Constants.Name.INTERVAL)
  public void setInterval(int intervalMS) {
    if (mViewPager != null && intervalMS > 0) {
      mViewPager.setIntervalTime(intervalMS);
    }
  }

  @WXComponentProp(name = Constants.Name.INDEX)
  public void setIndex(int index) {
    if (mViewPager != null && mAdapter != null) {
      if (index >= mAdapter.getRealCount() || index < 0) {
        initIndex = index;
        return;
      }

      index = getRealIndex(index);
      mViewPager.setCurrentItem(index);
      if (mIndicator != null && mIndicator.getHostView() != null
              && mIndicator.getHostView().getRealCurrentItem() != index) {
        //OnPageChangeListener not triggered
        WXLogUtils.d("setIndex >>>> correction indicator to " + index);
        mIndicator.getHostView().setRealCurrentItem(index);
        mIndicator.getHostView().invalidate();

        if (mPageChangeListener != null && mAdapter != null) {
          mPageChangeListener.onPageSelected(mAdapter.getFirst() + index);
        }
      }
    }
  }
  @WXComponentProp(name = Constants.Name.SCROLLABLE)
  public void setScrollable(boolean scrollable) {
    if (mViewPager != null && mAdapter != null) {
      mViewPager.setScrollable(scrollable);
    }
  }

  @WXComponentProp(name = Constants.Name.OFFSET_X_ACCURACY)
  public void setOffsetXAccuracy(float accuracy) {
    this.offsetXAccuracy = accuracy;
  }

  protected class SliderPageChangeListener implements OnPageChangeListener {

    private int lastPos = -1;

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int pos) {
      if (mAdapter.getRealPosition(pos) == lastPos) {
        return;
      }
      if (WXEnvironment.isApkDebugable()) {
        WXLogUtils.d("onPageSelected >>>>" + mAdapter.getRealPosition(pos) + " lastPos: " + lastPos);
      }
      if (mAdapter == null || mAdapter.getRealCount() == 0) {
        return;
      }

      int realPosition = mAdapter.getRealPosition(pos);
      if (mChildren == null || realPosition >= mChildren.size()) {
        return;
      }

      if (getEvents().size() == 0) {
        return;
      }
      WXEvent event = getEvents();
      String ref = getRef();
      if (event.contains(Constants.Event.CHANGE) && WXViewUtils.onScreenArea(getHostView())) {
        params.put(INDEX, realPosition);

        Map<String, Object> domChanges = new HashMap<>();
        Map<String, Object> attrsChanges = new HashMap<>();
        attrsChanges.put(INDEX, realPosition);
        domChanges.put("attrs", attrsChanges);
        WXSDKManager.getInstance().fireEvent(getInstanceId(), ref,
            Constants.Event.CHANGE, params, domChanges);
      }

      mViewPager.requestLayout();
      getHostView().invalidate();
      lastPos = mAdapter.getRealPosition(pos);
    }

    @Override
    public void onPageScrollStateChanged(int arg0) {
      FrameLayout root = getHostView();
      if (null != root) {
        root.invalidate();
      }
    }
  }

  protected static class SliderOnScrollListener implements OnPageChangeListener {
    private float lastPositionOffset = 99f;
    private int selectedPosition;
    private WXSlider target;

    public SliderOnScrollListener(WXSlider target) {
      this.target = target;
      this.selectedPosition = target.mViewPager.superGetCurrentItem();
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
      if (lastPositionOffset == 99f) {
        lastPositionOffset = positionOffset;
        return;
      }

      float offset = positionOffset - lastPositionOffset;

      if (Math.abs(offset) >= target.offsetXAccuracy) {
        if (position == selectedPosition) {
          //slide to left. positionOffset[0 -> 1]
          Map<String,Object> event = new HashMap<>(1);
          event.put(Constants.Name.OFFSET_X_RATIO, -positionOffset);
          target.fireEvent(Constants.Event.SCROLL, event);
        } else if (position < selectedPosition) {
          //slide to right. positionOffset[1 -> 0]
          Map<String,Object> event = new HashMap<>(1);
          event.put(Constants.Name.OFFSET_X_RATIO, (1f - positionOffset));
          target.fireEvent(Constants.Event.SCROLL, event);
        }
        lastPositionOffset = positionOffset;
      }
    }

    @Override
    public void onPageSelected(int position) {
      selectedPosition = position;
    }

    @Override
    public void onPageScrollStateChanged(int state) {

      /**
       * @homeblog@vip.qq.com
       *
       *  add scrollstart & scrollend event
       *
       */
      switch (state) {
        case ViewPager.SCROLL_STATE_IDLE:
          lastPositionOffset = 99f;
          target.fireEvent("scrollend");
          break;
        case ViewPager.SCROLL_STATE_DRAGGING:
          target.fireEvent("scrollstart");
          break;
        case ViewPager.SCROLL_STATE_SETTLING:
          break;

      }
    }
  }

  @SuppressLint("ClickableViewAccessibility")
  private void hackTwoItemsInfiniteScroll() {
    if (mViewPager == null || mAdapter == null) {
      return;
    }
    if (isInfinite) {
      if (mAdapter.getRealCount() == 2) {
        final GestureDetector gestureDetector = new GestureDetector(getContext(), new FlingGestureListener(mViewPager));
        mViewPager.setOnTouchListener(new View.OnTouchListener() {
          @Override
          public boolean onTouch(View v, MotionEvent event) {
            return gestureDetector.onTouchEvent(event);
          }
        });
      } else {
        mViewPager.setOnTouchListener(null);
      }
    }
  }

  private static class FlingGestureListener extends GestureDetector.SimpleOnGestureListener {
    private static final int SWIPE_MIN_DISTANCE = WXViewUtils.dip2px(50);
    private static final int SWIPE_MAX_OFF_PATH = WXViewUtils.dip2px(250);
    private static final int SWIPE_THRESHOLD_VELOCITY = WXViewUtils.dip2px(200);
    private WeakReference<WXCircleViewPager> pagerRef;

    FlingGestureListener(WXCircleViewPager pager) {
      this.pagerRef = new WeakReference<>(pager);
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
      WXCircleViewPager mViewPager = pagerRef.get();
      if (mViewPager == null) {
        return false;
      }

      try {
        if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) {
          return false;
        }

        if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE
                && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY
                && mViewPager.superGetCurrentItem() == 1) {
          // right to left swipe
          mViewPager.setCurrentItem(0, false);
          return true;
        } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
                && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY
                && mViewPager.superGetCurrentItem() == 0) {
          // left to right swipe
          mViewPager.setCurrentItem(1, false);
          return true;
        }
      } catch (Exception e) {
        // ignore
      }
      return false;
    }
  }
}
