package org.thoughtcrime.securesms.mediasend;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.signal.core.util.DimensionUnit;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;

public class CameraButtonView extends View {

  private enum CameraButtonMode { IMAGE, MIXED }

  private static final float        CAPTURE_ARC_STROKE_WIDTH       = 3.5f;
  private static final int          CAPTURE_FILL_PROTECTION        = 10;
  private static final int          PROGRESS_ARC_STROKE_WIDTH      = 4;
  private static final int          HALF_PROGRESS_ARC_STROKE_WIDTH = PROGRESS_ARC_STROKE_WIDTH / 2;
  private static final float        DEADZONE_REDUCTION_PERCENT     = 0.35f;
  private static final int          DRAG_DISTANCE_MULTIPLIER       = 3;
  private static final Interpolator ZOOM_INTERPOLATOR              = new DecelerateInterpolator();

  private final @NonNull Paint outlinePaint     = outlinePaint();
  private final @NonNull Paint backgroundPaint  = backgroundPaint();
  private final @NonNull Paint arcPaint         = arcPaint();
  private final @NonNull Paint recordPaint      = recordPaint();
  private final @NonNull Paint progressPaint    = progressPaint();
  private final @NonNull Paint captureFillPaint = captureFillPaint();

  private Animation growAnimation;
  private Animation shrinkAnimation;

  private boolean isRecordingVideo;
  private float   progressPercent = 0f;

  private @NonNull  CameraButtonMode     cameraButtonMode = CameraButtonMode.IMAGE;
  private @Nullable VideoCaptureListener videoCaptureListener;

  private final float imageCaptureSize;
  private final float recordSize;
  private final RectF progressRect = new RectF();
  private final Rect  deadzoneRect = new Rect();

  private final @NonNull OnLongClickListener internalLongClickListener = v -> {
    notifyVideoCaptureStarted();
    shrinkAnimation.cancel();
    setScaleX(1f);
    setScaleY(1f);
    isRecordingVideo = true;
    return true;
  };

  public CameraButtonView(@NonNull Context context) {
    this(context, null);
  }

  public CameraButtonView(@NonNull Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public CameraButtonView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraButtonView, defStyleAttr, 0);

    imageCaptureSize = a.getDimensionPixelSize(R.styleable.CameraButtonView_imageCaptureSize, -1);
    recordSize       = a.getDimensionPixelSize(R.styleable.CameraButtonView_recordSize, -1);
    a.recycle();

    initializeImageAnimations();
  }

  private static Paint recordPaint() {
    Paint recordPaint = new Paint();
    recordPaint.setColor(0xFFF44336);
    recordPaint.setAntiAlias(true);
    recordPaint.setStyle(Paint.Style.FILL);
    return recordPaint;
  }

  private static Paint outlinePaint() {
    Paint outlinePaint = new Paint();
    outlinePaint.setColor(0x26000000);
    outlinePaint.setAntiAlias(true);
    outlinePaint.setStyle(Paint.Style.STROKE);
    outlinePaint.setStrokeWidth(ViewUtil.dpToPx(4));
    return outlinePaint;
  }

  private static Paint backgroundPaint() {
    Paint backgroundPaint = new Paint();
    backgroundPaint.setColor(0x4CFFFFFF);
    backgroundPaint.setAntiAlias(true);
    backgroundPaint.setStyle(Paint.Style.FILL);
    return backgroundPaint;
  }

  private static Paint arcPaint() {
    Paint arcPaint = new Paint();
    arcPaint.setColor(0xFFFFFFFF);
    arcPaint.setAntiAlias(true);
    arcPaint.setStyle(Paint.Style.STROKE);
    arcPaint.setStrokeWidth(DimensionUnit.DP.toPixels(CAPTURE_ARC_STROKE_WIDTH));
    return arcPaint;
  }

  private static Paint captureFillPaint() {
    Paint arcPaint = new Paint();
    arcPaint.setColor(0xFFFFFFFF);
    arcPaint.setAntiAlias(true);
    arcPaint.setStyle(Paint.Style.FILL);
    return arcPaint;
  }


  private static Paint progressPaint() {
    Paint progressPaint = new Paint();
    progressPaint.setColor(0xFFFFFFFF);
    progressPaint.setAntiAlias(true);
    progressPaint.setStyle(Paint.Style.STROKE);
    progressPaint.setStrokeWidth(ViewUtil.dpToPx(PROGRESS_ARC_STROKE_WIDTH));
    progressPaint.setShadowLayer(4, 0, 2, 0x40000000);
    return progressPaint;
  }

  private void initializeImageAnimations() {
    shrinkAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.camera_capture_button_shrink);
    growAnimation   = AnimationUtils.loadAnimation(getContext(), R.anim.camera_capture_button_grow);

    shrinkAnimation.setFillAfter(true);
    shrinkAnimation.setFillEnabled(true);
    growAnimation.setFillAfter(true);
    growAnimation.setFillEnabled(true);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if (isRecordingVideo) {
      drawForVideoCapture(canvas);
    } else {
      drawForImageCapture(canvas);
    }
  }

  private void drawForImageCapture(Canvas canvas) {
    float centerX = getWidth() / 2f;
    float centerY = getHeight() / 2f;

    float radius = imageCaptureSize / 2f;
    canvas.drawCircle(centerX, centerY, radius, backgroundPaint);
    canvas.drawCircle(centerX, centerY, radius, arcPaint);
    canvas.drawCircle(centerX, centerY, radius - DimensionUnit.DP.toPixels(CAPTURE_FILL_PROTECTION), captureFillPaint);
  }

  private void drawForVideoCapture(Canvas canvas) {
    float centerX = getWidth() / 2f;
    float centerY = getHeight() / 2f;

    canvas.drawCircle(centerX, centerY, centerY, backgroundPaint);
    canvas.drawCircle(centerX, centerY, centerY, outlinePaint);

    canvas.drawCircle(centerX, centerY, recordSize / 2f, recordPaint);

    progressRect.top    = ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
    progressRect.left   = ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
    progressRect.right  = getWidth() - ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
    progressRect.bottom = getHeight() - ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);

    canvas.drawArc(progressRect, 270f, 360f * progressPercent, false, progressPaint);
  }

  public void setVideoCaptureListener(@Nullable VideoCaptureListener videoCaptureListener) {
    if (isRecordingVideo) throw new IllegalStateException("Cannot set video capture listener while recording");

    if (videoCaptureListener != null) {
      this.cameraButtonMode = CameraButtonMode.MIXED;
      this.videoCaptureListener = videoCaptureListener;
      super.setOnLongClickListener(internalLongClickListener);
    } else {
      this.cameraButtonMode = CameraButtonMode.IMAGE;
      this.videoCaptureListener = null;
      super.setOnLongClickListener(null);
    }
  }

  public void setProgress(float percentage) {
    progressPercent = Util.clamp(percentage, 0f, 1f);
    invalidate();
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (cameraButtonMode == CameraButtonMode.IMAGE) {
      return handleImageModeTouchEvent(event);
    }

    boolean eventWasHandled = handleVideoModeTouchEvent(event);
    int     action          = event.getAction();

    if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
      isRecordingVideo = false;
    }

    return eventWasHandled;
  }

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);

    getLocalVisibleRect(deadzoneRect);
    deadzoneRect.left   += (int) (getWidth()  * DEADZONE_REDUCTION_PERCENT / 2f);
    deadzoneRect.top    += (int) (getHeight() * DEADZONE_REDUCTION_PERCENT / 2f);
    deadzoneRect.right  -= (int) (getWidth()  * DEADZONE_REDUCTION_PERCENT / 2f);
    deadzoneRect.bottom -= (int) (getHeight() * DEADZONE_REDUCTION_PERCENT / 2f);
  }

  private boolean handleImageModeTouchEvent(MotionEvent event) {
    int action = event.getAction();
    switch (action) {
      case MotionEvent.ACTION_DOWN:
        if (isEnabled()) {
          startAnimation(shrinkAnimation);
          performClick();
        }
        return true;
      case MotionEvent.ACTION_UP:
        startAnimation(growAnimation);
        return true;
      default:
        return super.onTouchEvent(event);
    }
  }

  private boolean handleVideoModeTouchEvent(MotionEvent event) {
    int action = event.getAction();
    switch (action) {
      case MotionEvent.ACTION_DOWN:
        if (isEnabled()) {
          startAnimation(shrinkAnimation);
        }
      case MotionEvent.ACTION_MOVE:
        if (isRecordingVideo && eventIsNotInsideDeadzone(event)) {

          float maxRange  = getHeight() * DRAG_DISTANCE_MULTIPLIER;
          float deltaY    = Math.abs(event.getY() - deadzoneRect.top);
          float increment = Math.min(1f, deltaY / maxRange);

          notifyZoomPercent(ZOOM_INTERPOLATOR.getInterpolation(increment));
          invalidate();
        }
        break;
      case MotionEvent.ACTION_CANCEL:
      case MotionEvent.ACTION_UP:
        if (!isRecordingVideo) {
          startAnimation(growAnimation);
        }
        notifyVideoCaptureEnded();
        break;
    }

    return super.onTouchEvent(event);
  }

  private boolean eventIsNotInsideDeadzone(MotionEvent event) {
    return Math.round(event.getY()) < deadzoneRect.top;
  }

  private void notifyVideoCaptureStarted() {
    if (!isRecordingVideo && videoCaptureListener != null) {
      videoCaptureListener.onVideoCaptureStarted();
    }
  }

  private void notifyVideoCaptureEnded() {
    if (isRecordingVideo && videoCaptureListener != null) {
      videoCaptureListener.onVideoCaptureComplete();
    }
  }

  private void notifyZoomPercent(float percent) {
    if (isRecordingVideo && videoCaptureListener != null) {
      videoCaptureListener.onZoomIncremented(percent);
    }
  }

  interface VideoCaptureListener {
    void onVideoCaptureStarted();
    void onVideoCaptureComplete();
    void onZoomIncremented(float percent);
  }
}
