package example.easy.animations.view;

import android.content.Context;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.Transformation;
import android.widget.FrameLayout;

public class FlipLayout extends FrameLayout
    implements Animation.AnimationListener, View.OnClickListener, OnSwipeListener {
  public static final int ANIM_DURATION_MILLIS = 500;
  private static final Interpolator fDefaultInterpolator = new DecelerateInterpolator();
  private OnFlipListener listener;
  private FlipAnimator animator;
  private boolean isFlipped;
  private Direction direction;
  private OnSwipeTouchListener touchListener;
  private View frontView, backView;

  public FlipLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(context);
  }

  public FlipLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
  }

  public FlipLayout(Context context) {
    super(context);
    init(context);
  }

  private void init(Context context) {
    animator = new FlipAnimator();
    animator.setAnimationListener(this);
    animator.setInterpolator(fDefaultInterpolator);
    animator.setDuration(ANIM_DURATION_MILLIS);
    direction = Direction.DOWN;
    setSoundEffectsEnabled(true);
    touchListener = new OnSwipeTouchListener(context);
  }

  @Override protected void onFinishInflate() {
    super.onFinishInflate();

    if (getChildCount() > 2) {
      throw new IllegalStateException("FlipLayout can host only two direct children");
    }

    frontView = getChildAt(0);
    frontView.setOnTouchListener(touchListener);
    frontView.setOnClickListener(this);
    backView = getChildAt(1);
    backView.setOnTouchListener(touchListener);
    backView.setOnClickListener(this);
    touchListener.addSwipeListener(this);
    reset();
  }

  private void toggleView() {
    if (frontView == null || backView == null) {
      return;
    }

    if (isFlipped) {
      frontView.setVisibility(View.VISIBLE);
      backView.setVisibility(View.GONE);
    } else {
      frontView.setVisibility(View.GONE);
      backView.setVisibility(View.VISIBLE);
    }

    isFlipped = !isFlipped;
  }

  public void setOnFlipListener(OnFlipListener listener) {
    this.listener = listener;
  }

  public void reset() {
    isFlipped = false;
    direction = Direction.DOWN;
    frontView.setVisibility(View.VISIBLE);
    backView.setVisibility(View.GONE);
  }

  public void toggleUp() {
    direction = Direction.UP;
    startAnimation();
  }

  public void toggleDown() {
    direction = Direction.DOWN;
    startAnimation();
  }

  public void toggleLeft() {
    direction = Direction.LEFT;
    startAnimation();
  }

  public void toggleRight() {
    direction = Direction.RIGHT;
    startAnimation();
  }

  public void startAnimation() {
    animator.setVisibilitySwapped();
    startAnimation(animator);
  }

  @Override public void onAnimationStart(Animation animation) {
    if (listener != null) {
      listener.onFlipStart(this);
    }
  }

  @Override public void onAnimationEnd(Animation animation) {
    if (listener != null) {
      listener.onFlipEnd(this);
    }
    if (direction == Direction.UP) direction = Direction.DOWN;
    if (direction == Direction.DOWN) direction = Direction.UP;
    if (direction == Direction.LEFT) direction = Direction.RIGHT;
    if (direction == Direction.RIGHT) direction = Direction.LEFT;
  }

  @Override public void onAnimationRepeat(Animation animation) {
  }

  public void setAnimationListener(Animation.AnimationListener listener) {
    animator.setAnimationListener(listener);
  }

  @Override public void onClick(View view) {
    toggleDown();
  }

  @Override public void onSwipeLeft() {
    toggleLeft();
  }

  @Override public void onSwipeRight() {
    toggleRight();
  }

  @Override public void onSwipeUp() {
    toggleUp();
  }

  @Override public void onSwipeDown() {
    toggleDown();
  }

  private enum Direction {
    UP, DOWN, LEFT, RIGHT
  }

  public interface OnFlipListener {

    void onFlipStart(FlipLayout view);

    void onFlipEnd(FlipLayout view);
  }

  public class FlipAnimator extends Animation {

    private static final float EXPERIMENTAL_VALUE = 50.f;
    private Camera camera;
    private float centerX;
    private float centerY;
    private boolean visibilitySwapped;

    public FlipAnimator() {
      setFillAfter(true);
    }

    public void setVisibilitySwapped() {
      visibilitySwapped = false;
    }

    @Override public void initialize(int width, int height, int parentWidth, int parentHeight) {
      super.initialize(width, height, parentWidth, parentHeight);
      camera = new Camera();
      this.centerX = width / 2;
      this.centerY = height / 2;
    }

    @Override protected void applyTransformation(float interpolatedTime, Transformation t) {
      // Angle around the y-axis of the rotation at the given time. It is
      // calculated both in radians and in the equivalent degrees.
      final double radians = Math.PI * interpolatedTime;

      float degrees = (float) (180.0 * radians / Math.PI);

      if (direction == Direction.UP || direction == Direction.LEFT) {
        degrees = -degrees;
      }

      // Once we reach the midpoint in the animation, we need to hide the
      // source view and show the destination view. We also need to change
      // the angle by 180 degrees so that the destination does not come in
      // flipped around. This is the main problem with SDK sample, it does
      // not
      // do this.
      if (interpolatedTime >= 0.5f) {
        switch (direction) {
          case LEFT:
          case UP:
            degrees += 180.f;
            break;
          case RIGHT:
          case DOWN:
            degrees -= 180.f;
            break;
        }

        if (!visibilitySwapped) {
          toggleView();
          visibilitySwapped = true;
        }
      }

      final Matrix matrix = t.getMatrix();

      camera.save();
      //you can delete this line, it move camera a little far from view and get back
      camera.translate(0.0f, 0.0f, (float) (EXPERIMENTAL_VALUE * Math.sin(radians)));
      switch (direction) {
        case DOWN:
        case UP:
          camera.rotateX(degrees);
          camera.rotateY(0);
          break;
        case LEFT:
        case RIGHT:
          camera.rotateY(degrees);
          camera.rotateX(0);
          break;
      }
      camera.rotateZ(0);
      camera.getMatrix(matrix);
      camera.restore();

      matrix.preTranslate(-centerX, -centerY);
      matrix.postTranslate(centerX, centerY);
    }
  }
}