package net.neevek.android.lib.paginize;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;

import net.neevek.android.lib.paginize.anim.PageAnimator;

import java.util.HashMap;
import java.util.Map;

/**
 * Copyright (c) 2015 neevek <[email protected]>
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * This is the main view container
 */
class ContainerViewManager {
  final static int SWIPE_TRANSITION_ANIMATION_DURATION = 500;
  private final static int SHADOW_VIEW_WIDTH = 20;                // in DIP
  private final static int SWIPE_TO_HIDE_THRESHOLD = 60;          // in DIP
  private final static int SWIPE_TO_HIDE_EDGE_SLOPE = 50;         // in DIP

  private PageManager mPageManager;
  private SwipeableContainerView mSwipeableContainerView;
  private Map<String, Animation> mAnimationCache = new HashMap<String,Animation>();

  ContainerViewManager(PageManager pageManager) {
    mPageManager = pageManager;
  }

  private void animateView(View view, int fromXType, float fromXValue,
                   int toXType, float toXValue,
                   Animation.AnimationListener animationListener) {
    animateView(view, fromXType, fromXValue, toXType, toXValue, false,
        animationListener);
  }

  void animateView(View view, int fromXType, float fromXValue,
                   int toXType, float toXValue, boolean cacheAnimationObj,
                   Animation.AnimationListener animationListener) {
    ViewGroup.MarginLayoutParams lp =
        (ViewGroup.MarginLayoutParams)view.getLayoutParams();
    lp.leftMargin = 0;
    lp.rightMargin = 0;
    view.requestLayout();

    Animation animation = null;
    if (cacheAnimationObj) {
      String key = new StringBuilder()
              .append(fromXType).append('-').append(fromXValue).append(',')
              .append(toXType).append('-').append(toXValue).toString();
      animation = mAnimationCache.get(key);
      if (animation == null) {
        animation = createAnimation(fromXType, fromXValue, toXType, toXValue,
            animationListener);
        mAnimationCache.put(key, animation);
      }
    }

    if (animation == null) {
      animation = createAnimation(fromXType, fromXValue, toXType, toXValue,
          animationListener);
    }

    view.startAnimation(animation);
  }

  private Animation createAnimation(
      int fromXType, float fromXValue,
      int toXType, float toXValue,
      Animation.AnimationListener animationListener) {
    Animation animation = new TranslateAnimation(fromXType, fromXValue, toXType,
        toXValue, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
    animation.setDuration(mPageManager.getTransitionAnimationDuration());
    animation.setInterpolator(new DecelerateInterpolator(2.0f));
    animation.setAnimationListener(animationListener);
    return animation;
  }

  void animateShadowViewForHiding(
      int anchorLeft, PageAnimator.AnimationDirection animationDirection) {
    mSwipeableContainerView.mShadowView.setVisibility(View.VISIBLE);
    mSwipeableContainerView.mShadowView.bringToFront();

    Animation.AnimationListener animationListener =
        new Animation.AnimationListener() {
          public void onAnimationStart(Animation animation) {
          }

          public void onAnimationRepeat(Animation animation) {
          }

          public void onAnimationEnd(Animation animation) {
            mSwipeableContainerView.mShadowView.setVisibility(View.INVISIBLE);
          }
        };

    if (animationDirection == PageAnimator.AnimationDirection.FROM_LEFT) {
      animateView(mSwipeableContainerView.mShadowView, Animation.ABSOLUTE,
          anchorLeft - mSwipeableContainerView.mShadowView.getWidth(),
          Animation.RELATIVE_TO_PARENT, 1f, animationListener);
    } else {
      animateView(mSwipeableContainerView.mShadowView,
          Animation.RELATIVE_TO_PARENT, 1, Animation.ABSOLUTE,
          -mSwipeableContainerView.getWidth(), animationListener);
    }
  }

  void animateShadowViewForShowing(
      PageAnimator.AnimationDirection animationDirection) {
    mSwipeableContainerView.mShadowView.setVisibility(View.VISIBLE);
    mSwipeableContainerView.mShadowView.bringToFront();

    Animation.AnimationListener animationListener =
        new Animation.AnimationListener() {
          public void onAnimationStart(Animation animation) {
          }

          public void onAnimationRepeat(Animation animation) {
          }

          public void onAnimationEnd(Animation animation) {
            mSwipeableContainerView.mShadowView.setVisibility(View.INVISIBLE);
          }
        };

    if (animationDirection == PageAnimator.AnimationDirection.FROM_RIGHT) {
      animateView(mSwipeableContainerView.mShadowView, Animation.ABSOLUTE,
          mSwipeableContainerView.getWidth() - mSwipeableContainerView.mShadowView.getWidth(),
          Animation.RELATIVE_TO_SELF, -1, animationListener);
    } else {
      animateView(mSwipeableContainerView.mShadowView, Animation.RELATIVE_TO_SELF,
          -1, Animation.ABSOLUTE,
          mSwipeableContainerView.getWidth() - mSwipeableContainerView.mShadowView.getWidth(),
          animationListener);
    }
  }

  SwipeableContainerView createContainerView(Context context) {
    mSwipeableContainerView = new SwipeableContainerView(context);
    return mSwipeableContainerView;
  }

  void enableSwipeToHide(boolean applyInsetsToShadow) {
    if (mSwipeableContainerView != null) {
      mSwipeableContainerView.enableSwipeToHide(applyInsetsToShadow);
    }
  }

  boolean canSwipeToHide() {
    return mSwipeableContainerView != null &&
        mSwipeableContainerView.mSwipeToHide;
  }

  private class SwipeableContainerView extends FrameLayout {
    private ShadowView mShadowView;
    private View mCurrentView;
    private View mPrevView;
    private MarginLayoutParams mShadowViewLP;
    private MarginLayoutParams mCurrentViewLP;
    private MarginLayoutParams mPrevViewLP;

    private int mInitialX;
    private boolean mIsDragging;
    private float mTouchSlope;
    private float mEdgeSlope;
    private int mSwipeToHideThreshold;

    private boolean mSwipeToHide;
    private boolean mApplyInsetsToShadow;

    public SwipeableContainerView(Context context) {
      super(context);
      mTouchSlope = ViewConfiguration.get(context).getScaledTouchSlop();
      mEdgeSlope = (int)(SWIPE_TO_HIDE_EDGE_SLOPE *
          getResources().getDisplayMetrics().density);
      mSwipeToHideThreshold = (int)(SWIPE_TO_HIDE_THRESHOLD *
          getResources().getDisplayMetrics().density);
    }

    @TargetApi(20)
    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
      if (insets != null) {
        if (mApplyInsetsToShadow && mShadowView != null) {
          MarginLayoutParams lp = (MarginLayoutParams)mShadowView.getLayoutParams();
          if (lp.topMargin != insets.getSystemWindowInsetTop()) {
            lp.topMargin = insets.getSystemWindowInsetTop();
          }
        }

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; ++i) {
          getChildAt(i).dispatchApplyWindowInsets(insets);
        }
      }
      return insets;
    }

    public void enableSwipeToHide(boolean applyInsetsToShadow) {
      if (!mSwipeToHide) {
        mSwipeToHide = true;
        mApplyInsetsToShadow = applyInsetsToShadow;

        mShadowView = new ShadowView(getContext());
        addView(mShadowView, new MarginLayoutParams(
            (int)(SHADOW_VIEW_WIDTH*getResources().getDisplayMetrics().density),
            ViewGroup.LayoutParams.MATCH_PARENT));

        mShadowView.setVisibility(INVISIBLE);
      }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
      if (!mSwipeToHide) {
        return super.onInterceptTouchEvent(ev);
      }

      int pageCount = mPageManager.getPageCount();
      if (pageCount < 2) {
        return super.onInterceptTouchEvent(ev);
      }

      if (!mPageManager.getTopPage().canSwipeToHide()) {
        return super.onInterceptTouchEvent(ev);
      }

      final int x = (int)ev.getRawX();
      switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
          mInitialX = x;
          break;
        case MotionEvent.ACTION_MOVE:
          if (!mIsDragging &&
              mPageManager.getTopPage().getView().getAnimation() == null &&
              mInitialX <= mEdgeSlope && x - mInitialX > mTouchSlope) {
            mIsDragging = true;

            Page topPage = mPageManager.getTopPage();
            mCurrentView = topPage.getView();
            int index = pageCount - topPage.getDefaultPageCountToPop() - 1;
            if (index >= 0) {
              mPrevView = mPageManager.getPageStack().get(index).getView();
              mPrevView.setVisibility(VISIBLE);
              mPrevViewLP = (MarginLayoutParams)mPrevView.getLayoutParams();
            }

            mCurrentViewLP = (MarginLayoutParams)mCurrentView.getLayoutParams();
            mShadowViewLP = (MarginLayoutParams)mShadowView.getLayoutParams();

            mInitialX = x;
            mShadowView.bringToFront();
            post(new Runnable() {
              @Override
              public void run() {
                mShadowViewLP.leftMargin = -mShadowView.getWidth();
                mShadowView.setVisibility(VISIBLE);
                mShadowView.requestLayout();
              }
            });
            return true;
          }
          break;
      }
      return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
      if (!mSwipeToHide) {
        return super.onTouchEvent(event);
      }

      int x = (int)event.getRawX();
      switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE:
          if (mIsDragging) {
            int delta = x - mInitialX;
            if (delta < 0) {
              delta = 0;
            }

            if (mPrevView != null) {
              mPrevViewLP.leftMargin = -(int)(getWidth() * 0.5f - (delta * 0.5f));
              mPrevViewLP.rightMargin = -(-(int)(getWidth() * 0.5f - (delta * 0.5f)));
            }
            if (mCurrentView != null) {
              mCurrentViewLP.leftMargin = delta;
              mCurrentViewLP.rightMargin = -delta;
              mShadowViewLP.leftMargin = delta - mShadowView.getWidth();
            }

            mSwipeableContainerView.requestLayout();

            return true;
          }
          break;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
          if (mIsDragging) {
            mIsDragging = false;
            int currentViewLeft = mCurrentView.getLeft();

            if (currentViewLeft > mSwipeToHideThreshold) {
              mPageManager.popTopNPages(
                  mPageManager.getTopPage().getDefaultPageCountToPop(), true);
            } else if (currentViewLeft > 0) {
              cancelSwipeToHide();
            } else {
              mShadowView.setVisibility(INVISIBLE);
            }

            return true;
          }
          break;

      }

      return super.onTouchEvent(event);
    }

    private void cancelSwipeToHide() {
      if (mPrevView != null) {
        animateView(mPrevView, Animation.ABSOLUTE,
            mPrevView.getLeft(), Animation.RELATIVE_TO_PARENT, -0.5f, null);
      }
      animateView(mCurrentView, Animation.ABSOLUTE,
          mCurrentView.getLeft(), Animation.RELATIVE_TO_PARENT, 0, null);
      animateView(mShadowView, Animation.ABSOLUTE,
          mShadowView.getLeft(), Animation.ABSOLUTE, -mShadowView.getWidth(),
          new Animation.AnimationListener() {
            public void onAnimationStart(Animation animation) { }
            public void onAnimationRepeat(Animation animation) { }
            public void onAnimationEnd(Animation animation) {
              mShadowView.setVisibility(View.INVISIBLE);
            }
          });
    }
  }

  private class ShadowView extends View {
    public ShadowView(Context context) {
      super(context);
      setGradientBackground();
    }

    private void setGradientBackground() {
      GradientDrawable bg = new GradientDrawable(
          GradientDrawable.Orientation.LEFT_RIGHT,
          new int[] { 0x00000000, 0x20000000 });
      if (Build.VERSION.SDK_INT >= 16) {
        setBackground(bg);
      } else {
        setBackgroundDrawable(bg);
      }
    }
  }
}