package com.vpaliy.fabexploration.player;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;

import io.codetail.animation.ViewAnimationUtils;
import io.codetail.widget.RevealFrameLayout;

import com.vpaliy.fabexploration.BaseFragment;
import com.vpaliy.fabexploration.R;

import java.util.List;

import butterknife.BindView;
import butterknife.BindViews;
import butterknife.OnClick;

import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;

public class PlayerFragment extends BaseFragment {

  @BindView(R.id.fab)
  protected FloatingActionButton actionButton;

  @BindView(R.id.parent)
  protected ViewGroup parent;

  @BindView(R.id.background)
  protected View background;

  @BindView(R.id.controls_panel)
  protected ViewGroup panel;

  @BindViews({R.id.album, R.id.track_author})
  protected List<View> fadeViews;

  @BindView(R.id.seekbar)
  protected SeekBar seekBar;

  @BindView(R.id.divider)
  protected View divider;

  @BindView(R.id.bottom_background)
  protected View bottomBackground;

  @BindView(R.id.track_title)
  protected TextView trackTitle;

  @BindView(R.id.sound_play)
  protected ImageView soundPlay;

  @BindView(R.id.action_bar)
  protected Toolbar actionBar;

  @BindView(R.id.play_pause)
  protected ImageView playPause;

  @BindView(R.id.next)
  protected ImageView next;

  @BindView(R.id.prev)
  protected ImageView prev;

  @BindView(R.id.controls)
  protected RevealFrameLayout revealContainer;

  private Animator revealAnimator;

  @Override
  protected int mainRes() {
    return R.layout.fragment_player;
  }

  @Override
  public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if (view != null) {
      setUpActionBar();
      setUpPanel();
      setUpButton();
    }
  }

  private void setUpButton() {
    divider.post(() -> {
      float offsetY = divider.getY() - (actionButton.getY() + actionButton.getHeight() / 2);
      actionButton.setTranslationY(offsetY);
    });
  }


  private void setUpPanel() {
    panel.post(() -> {
      panel.setOnClickListener(v -> {
        final int w = panel.getWidth();
        final int h = panel.getHeight();
        final int endRadius = (int) Math.hypot(w, h);
        final float offsetY = (actionButton.getY() + actionButton.getHeight() / 2) - divider.getTop();
        final int cx = (int) (actionButton.getX() + actionButton.getWidth() / 2);
        final int cy = (int) (offsetY);
        setUpPlayDrawable();
        revealAnimator = ViewAnimationUtils.createCircularReveal(panel, cx, cy, endRadius, actionButton.getHeight() / 2);
        revealAnimator.removeAllListeners();
        revealAnimator.addListener(new AnimatorListenerAdapter() {
          @Override
          public void onAnimationStart(Animator animation) {
            super.onAnimationStart(animation);
            ViewCompat.setElevation(actionButton, 0);
            fadeInOutViews(1, duration(R.integer.fade_duration));
            actionButton.setVisibility(View.VISIBLE);
            actionButton.animate()
                    .alpha(1)
                    .setDuration(duration(R.integer.fade_in_duration))
                    .setListener(null)
                    .start();
          }

          @Override
          public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            panel.setVisibility(View.GONE);
            backAnimation();
            divider.animate()
                    .setDuration(duration(R.integer.fade_in_duration))
                    .scaleY(1).start();
            bottomBackground.setPivotY(0);
            bottomBackground.animate()
                    .setDuration(duration(R.integer.fade_in_duration))
                    .scaleY(0).start();
            runIconScale(0, R.drawable.ic_volume_bottom,
                    ContextCompat.getColor(getContext(), R.color.color_grey));
            setUpReveal();
          }
        });
        revealAnimator.setDuration(duration(R.integer.fade_in_duration));
        revealAnimator.start();
      });
      panel.setVisibility(View.GONE);
    });
  }

  private void setUpActionBar() {
    actionBar.inflateMenu(R.menu.main);
  }

  private void setUpReveal() {
    int w = panel.getWidth();
    int h = panel.getHeight();
    final int endRadius = (int) Math.hypot(w, h);
    final int cx = (int) (actionButton.getX() + actionButton.getWidth() / 2);
    final int cy = (int) (actionButton.getY() + actionButton.getHeight() / 2 - background.getTop());

    final float deltaX = cx - (playPause.getLeft() + playPause.getWidth() / 2);
    final float deltaY = (cy - getResources().getDimension(R.dimen.play_pause_size) / 2) - (playPause.getTop());
    playPause.setTranslationX(deltaX);
    playPause.setTranslationY(deltaY);
    revealAnimator = ViewAnimationUtils.createCircularReveal(panel, cx, cy, actionButton.getHeight(), endRadius);
    revealAnimator.addListener(new AnimatorListenerAdapter() {
      @Override
      public void onAnimationStart(Animator animation) {
        panel.setVisibility(View.VISIBLE);
        actionButton.setVisibility(View.INVISIBLE);
        fadeInOutViews(0, duration(R.integer.fade_in_duration));
      }
    });
    revealAnimator.setDuration(duration(R.integer.conceal_duration) / 2);
    revealAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
  }

  private void runButtonAnimation() {
    next.setScaleX(0);
    next.setScaleY(0);
    prev.setScaleX(0);
    prev.setScaleY(0);
    Path arcPath = createArcPath(playPause, 0, 0, -playPause.getTranslationY());
    ValueAnimator pathAnimator = ValueAnimator.ofFloat(0, 1);
    pathAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      private float point[] = new float[2];
      private PathMeasure pathMeasure = new PathMeasure(arcPath, false);

      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        final float value = animation.getAnimatedFraction();
        // Gets the point at the fractional path length
        pathMeasure.getPosTan(pathMeasure.getLength() * value, point, null);

        // Sets view location to the above point
        playPause.setTranslationX(point[0]);
        playPause.setTranslationY(point[1]);
      }
    });
    pathAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
    pathAnimator.setDuration(duration(R.integer.path_duration) / 2);
    pathAnimator.start();
    next.animate()
            .setDuration(duration(R.integer.scale_duration))
            .setStartDelay(duration(R.integer.short_delay))
            .scaleX(1).scaleY(1)
            .start();
    prev.animate()
            .setDuration(duration(R.integer.scale_duration))
            .setStartDelay(duration(R.integer.short_delay))
            .scaleX(1).scaleY(1)
            .start();
  }

  private void setUpPauseDrawable() {
    Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.ic_pause);
    actionButton.setImageDrawable(drawable);
    playPause.setImageDrawable(drawable);
  }

  private void setUpPlayDrawable() {
    Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.ic_play);
    actionButton.setImageDrawable(drawable);
    playPause.setImageDrawable(drawable);
  }

  @OnClick(R.id.fab)
  public void onButtonClick() {
    setUpPauseDrawable();
    final float playPauseY = playPause.getY() + background.getY();
    float endX = background.getWidth() / 2;
    float endY = playPauseY + playPause.getHeight() / 2;
    float startX = 0;
    float startY = 0;
    final float curveRadius = background.getHeight() / 3;

    final float offsetX = endX - (actionButton.getX() + actionButton.getWidth() / 2);
    final float offsetY = endY - (actionButton.getY() + actionButton.getHeight() / 2);

    endX = offsetX;
    endY = offsetY;

    Path arcPath = new Path();

    float midX = startX + ((endX - startX) / 2);
    float midY = startY + ((endY - startY) / 2);
    float xDiff = midX - startX;
    float yDiff = midY - startY;

    double angle = (Math.atan2(yDiff, xDiff) * (180 / Math.PI)) - 90;
    double angleRadians = Math.toRadians(angle);

    float pointX = (float) (midX + curveRadius * Math.cos(angleRadians));
    float pointY = (float) (midY + curveRadius * Math.sin(angleRadians));

    arcPath.moveTo(0, 0);
    arcPath.cubicTo(0, 0, pointX, pointY, endX, endY);

    ValueAnimator pathAnimator = ValueAnimator.ofFloat(0, 1);
    pathAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      private float point[] = new float[2];
      private volatile boolean isFired;
      private PathMeasure pathMeasure = new PathMeasure(arcPath, false);

      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        final float value = animation.getAnimatedFraction();
        // Gets the point at the fractional path length
        pathMeasure.getPosTan(pathMeasure.getLength() * value, point, null);

        // Sets view location to the above point
        actionButton.setTranslationX(point[0]);
        actionButton.setTranslationY(point[1]);

        if (!isFired) {
          if (animation.getAnimatedFraction() >= 0.35) {
            isFired = true;
            setUpReveal();
            runButtonAnimation();
            //reveal and animate the thumb
            runRevealNProgress();
            //stretch out the top divider
            runTopDividerScale();
            //expand the bottom divider
            runBottomDividerScale();
            //scale icon, swap icon, scale icon
            runIconScale(0, R.drawable.ic_play_bottom, Color.WHITE);
          }
        }
      }
    });
    pathAnimator.setInterpolator(new DecelerateInterpolator());
    pathAnimator.setDuration(duration(R.integer.path_duration));
    pathAnimator.start();
  }

  private void runRevealNProgress() {
    revealAnimator.setDuration(duration(R.integer.conceal_duration));
    revealAnimator.setInterpolator(new DecelerateInterpolator());
    seekBar.setProgress(80);
    ObjectAnimator progressAnimator = ObjectAnimator.ofInt(seekBar, "progress", 80, 20);
    ObjectAnimator scaleY = ObjectAnimator.ofFloat(seekBar, View.SCALE_Y, 0, 1f);
    progressAnimator.setInterpolator(new DecelerateInterpolator());
    progressAnimator.setDuration(duration(R.integer.progress_duration));
    scaleY.setDuration(duration(R.integer.progress_duration));
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.play(revealAnimator);
    animatorSet.play(progressAnimator).with(scaleY);
    animatorSet.start();
  }

  private void fadeInOutViews(int alpha, int duration) {
    for (final View view : fadeViews) {
      view.animate()
              .alpha(alpha)
              .setDuration(duration)
              .setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                  super.onAnimationEnd(animation);
                  view.setVisibility(alpha < 1 ? View.INVISIBLE : View.VISIBLE);
                }
              }).start();
    }
  }

  private void runIconScale(int delay, @DrawableRes int drawable, int color) {
    soundPlay.animate()
            .scaleY(0)
            .scaleX(0)
            .setDuration(duration(R.integer.short_delay))
            .setStartDelay(delay)
            .setListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                trackTitle.setTextColor(color);
                soundPlay.setImageDrawable(ContextCompat.getDrawable(getContext(), drawable));
                soundPlay.animate()
                        .scaleX(1)
                        .scaleY(1)
                        .setDuration(duration(R.integer.scale_duration))
                        .setListener(null).start();
              }
            }).start();
  }

  private void runBottomDividerScale() {
    bottomBackground.setPivotY(0);
    bottomBackground.animate()
            .setDuration(duration(R.integer.divider_duration))
            .scaleY(100).start();
  }

  private void runTopDividerScale() {
    divider.animate()
            .setDuration(duration(R.integer.divider_duration))
            .scaleY(30).start();
  }


  private void backAnimation() {
    float endX = 0;
    float endY = 0;
    float startX = actionButton.getTranslationX();
    float startY = actionButton.getTranslationY();
    final float curveRadius = -background.getHeight() / 2;

    Path arcPath = new Path();

    float midX = startX + ((endX - startX) / 2);
    float midY = startY + ((endY - startY) / 2);
    float xDiff = midX - startX;
    float yDiff = midY - startY;

    double angle = (Math.atan2(yDiff, xDiff) * (180 / Math.PI)) - 90;
    double angleRadians = Math.toRadians(angle);

    float pointX = (float) (midX + curveRadius * Math.cos(angleRadians));
    float pointY = (float) (midY + curveRadius * Math.sin(angleRadians));

    arcPath.moveTo(startX, startY);
    arcPath.cubicTo(startX, startY, pointX, pointY, endX, endY);

    ValueAnimator pathAnimator = ValueAnimator.ofFloat(0, 1);
    pathAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      private float point[] = new float[2];
      private PathMeasure pathMeasure = new PathMeasure(arcPath, false);

      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        final float value = animation.getAnimatedFraction();
        // Gets the point at the fractional path length
        pathMeasure.getPosTan(pathMeasure.getLength() * value, point, null);

        // Sets view location to the above point
        actionButton.setTranslationX(point[0]);
        actionButton.setTranslationY(point[1]);
      }
    });
    pathAnimator.setInterpolator(new DecelerateInterpolator());
    pathAnimator.setDuration(duration(R.integer.path_duration));
    pathAnimator.start();
  }
}