package com.lemenzo.gallery.viewer;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;

public final class ZoomableImageView extends ImageView {
    public interface Listener {
        void onSingleTap();
        void onSwipeLeft();
        void onSwipeRight();
    }

    private static final float MAX_ZOOM = 5.0f;
    private static final float DOUBLE_TAP_ZOOM = 2.5f;

    private final Matrix imageMatrixValue = new Matrix();
    private ScaleGestureDetector scaleDetector;
    private GestureDetector gestureDetector;
    private float baseScale = 1.0f;
    private float userScale = 1.0f;
    private float lastX;
    private float lastY;
    private Listener listener;

    public ZoomableImageView(Context context) {
        this(context, null);
    }

    public ZoomableImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setScaleType(ScaleType.MATRIX);

        scaleDetector = new ScaleGestureDetector(context,
                new ScaleGestureDetector.SimpleOnScaleGestureListener() {
                    @Override
                    public boolean onScale(ScaleGestureDetector detector) {
                        float target = clamp(userScale * detector.getScaleFactor(), 1.0f, MAX_ZOOM);
                        float factor = target / userScale;
                        userScale = target;
                        imageMatrixValue.postScale(factor, factor,
                                detector.getFocusX(), detector.getFocusY());
                        constrainTranslation();
                        setImageMatrix(imageMatrixValue);
                        return true;
                    }
                });

        gestureDetector = new GestureDetector(context,
                new GestureDetector.SimpleOnGestureListener() {
                    @Override
                    public boolean onDown(MotionEvent event) {
                        return true;
                    }

                    @Override
                    public boolean onSingleTapConfirmed(MotionEvent event) {
                        if (listener != null) listener.onSingleTap();
                        return true;
                    }

                    @Override
                    public boolean onDoubleTap(MotionEvent event) {
                        float target = userScale < 1.5f ? DOUBLE_TAP_ZOOM : 1.0f;
                        if (target == 1.0f) {
                            fitToView();
                        } else {
                            float factor = target / userScale;
                            userScale = target;
                            imageMatrixValue.postScale(factor, factor, event.getX(), event.getY());
                            constrainTranslation();
                            setImageMatrix(imageMatrixValue);
                        }
                        return true;
                    }

                    @Override
                    public boolean onFling(MotionEvent start, MotionEvent end,
                            float velocityX, float velocityY) {
                        if (listener == null || userScale > 1.02f) return false;
                        float dx = end.getX() - start.getX();
                        float dy = end.getY() - start.getY();
                        if (Math.abs(dx) < 120f || Math.abs(dx) < Math.abs(dy) * 1.3f
                                || Math.abs(velocityX) < 500f) {
                            return false;
                        }
                        if (dx < 0) listener.onSwipeLeft();
                        else listener.onSwipeRight();
                        return true;
                    }
                });
    }

    public void setListener(Listener listener) {
        this.listener = listener;
    }

    public float getZoom() {
        return userScale;
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        super.onSizeChanged(width, height, oldWidth, oldHeight);
        fitToView();
    }

    @Override
    public void setImageBitmap(Bitmap bitmap) {
        super.setImageBitmap(bitmap);
        post(this::fitToView);
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        post(this::fitToView);
    }

    private void fitToView() {
        Drawable drawable = getDrawable();
        if (drawable == null || getWidth() == 0 || getHeight() == 0
                || drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
            return;
        }

        baseScale = Math.min(
                (float) getWidth() / drawable.getIntrinsicWidth(),
                (float) getHeight() / drawable.getIntrinsicHeight());
        userScale = 1.0f;

        float drawnWidth = drawable.getIntrinsicWidth() * baseScale;
        float drawnHeight = drawable.getIntrinsicHeight() * baseScale;
        float dx = (getWidth() - drawnWidth) / 2.0f;
        float dy = (getHeight() - drawnHeight) / 2.0f;

        imageMatrixValue.reset();
        imageMatrixValue.postScale(baseScale, baseScale);
        imageMatrixValue.postTranslate(dx, dy);
        setImageMatrix(imageMatrixValue);
    }

    private void constrainTranslation() {
        Drawable drawable = getDrawable();
        if (drawable == null) return;

        RectF rect = new RectF(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        imageMatrixValue.mapRect(rect);

        float dx = 0f;
        float dy = 0f;

        if (rect.width() <= getWidth()) {
            dx = getWidth() / 2f - rect.centerX();
        } else if (rect.left > 0) {
            dx = -rect.left;
        } else if (rect.right < getWidth()) {
            dx = getWidth() - rect.right;
        }

        if (rect.height() <= getHeight()) {
            dy = getHeight() / 2f - rect.centerY();
        } else if (rect.top > 0) {
            dy = -rect.top;
        } else if (rect.bottom < getHeight()) {
            dy = getHeight() - rect.bottom;
        }

        imageMatrixValue.postTranslate(dx, dy);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        scaleDetector.onTouchEvent(event);
        gestureDetector.onTouchEvent(event);

        if (event.getPointerCount() == 1 && !scaleDetector.isInProgress()) {
            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                lastX = event.getX();
                lastY = event.getY();
            } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE && userScale > 1.01f) {
                float dx = event.getX() - lastX;
                float dy = event.getY() - lastY;
                imageMatrixValue.postTranslate(dx, dy);
                constrainTranslation();
                setImageMatrix(imageMatrixValue);
                lastX = event.getX();
                lastY = event.getY();
            }
        }
        return true;
    }

    private static float clamp(float value, float min, float max) {
        return Math.max(min, Math.min(max, value));
    }
}
