package com.adealink.weparty.imageselect.clip;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.animation.LinearInterpolator;

import androidx.exifinterface.media.ExifInterface;

import com.adealink.frame.log.Log;
import com.adealink.frame.util.DisplayUtil;
import com.adealink.frame.util.ImageUtil;
import com.adealink.weparty.R;

import java.io.IOException;


public class ClipImageView extends androidx.appcompat.widget.AppCompatImageView {

    public static final String TAG = "ClipImageView";

    public static final String HORIZONTAL_PADDING = "horizontal_padding";
    public static final String WIDTH_HEIGHT_RATIO = "width_height_ratio";

    private int horizontalPadding = 20;
    private double widthHeightRatio = 3.0/4.0;

    private Context mContext = null;
    private Bitmap mBitmap;
    private Bitmap savedBitmap;

    private Matrix mMatrix = null;

    private float mViewWidth;
    private float mViewHeight;
    private float mBitmapWidth;
    private float mBitmapHeight;
    private float mCurrentBmWidth;
    private float mCurrentBmHeight;

    private Rect mDrawingRect = new Rect();

    // 裁剪框
    private Paint mOutlinePaint = new Paint();
    private Path mOutlinePath = new Path();
    private Rect mOutlineRect = new Rect();
    private RectF mOutlineRectF = new RectF();

    private Paint mGrayPaint = new Paint();
    private Rect mGrayTopRect = new Rect();
    private Rect mGrayBottomRect = new Rect();
    private Rect mGrayLeftRect = new Rect();
    private Rect mGrayRightRect = new Rect();

    private int mOutlineRectLeft = -1;
    private int mOutlineRectTop = -1;
    private int mOutlineRectRight = -1;
    private int mOutlineRectBottom = -1;

    private float mMinTranslateX;
    private float mMinTranslateY;

    private int mMaxTranslateX;
    private int mMaxTranslateY;

    private float mMinScale;

    private boolean isAnimating = false;
    private int strokeColor;
    private float strokeWidth;


    private float originMidX;
    private float originMidY;
    private float originX = -1;
    private float originY = -1;
    private float lastX0 = -1;
    private float lastY0;
    private float lastX1 = -1;
    private float lastY1;

    private float originScale = 1;

    public ClipImageView(Context context) {
        super(context);
        initAttribute(context, null);
        mContext = context;
    }


    public ClipImageView(Context context, AttributeSet paramAttributeSet) {
        super(context, paramAttributeSet);
        mContext = context;
        initAttribute(context, paramAttributeSet);
    }

    private void initAttribute(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.ClipCorner);
            strokeColor = t.getColor(R.styleable.ClipCorner_ClipBorderColor, Color.WHITE);
            strokeWidth = t.getDimension(R.styleable.ClipCorner_StrokeWidth, 3f);
            t.recycle();
        }
    }

    private void init() {

        mOutlinePaint.setStrokeWidth(strokeWidth);
        mOutlinePaint.setStyle(Paint.Style.STROKE);
        mOutlinePaint.setAntiAlias(true);
        mOutlinePaint.setColor(strokeColor);
        getDrawingRect(mDrawingRect);

        mOutlineRectLeft = DisplayUtil.dp2px(horizontalPadding);
        int rectWidth = mDrawingRect.right - mDrawingRect.left - mOutlineRectLeft * 2;
        int rectHeight = (int) (rectWidth/widthHeightRatio);
        mOutlineRectRight = mOutlineRectLeft + rectWidth;
        mOutlineRectTop = (int) ((mDrawingRect.bottom - mDrawingRect.top - rectHeight) / 2.0);
        mOutlineRectBottom = mOutlineRectTop + rectHeight;

        mOutlineRect.set(mOutlineRectLeft, mOutlineRectTop, mOutlineRectRight, mOutlineRectBottom);

        mOutlineRectF.set(mOutlineRect);
        mOutlinePath.addRect(mOutlineRectF, Path.Direction.CW);

        mGrayPaint.setColor(Color.parseColor("#b3000000"));
        mGrayTopRect = new Rect(mDrawingRect.left, mDrawingRect.top, mDrawingRect.right, mOutlineRect.top);
        mGrayBottomRect = new Rect(mDrawingRect.left, mOutlineRect.bottom, mDrawingRect.right, mDrawingRect.bottom);
        mGrayLeftRect = new Rect(mDrawingRect.left, mOutlineRect.top, mOutlineRect.left, mOutlineRect.bottom);
        mGrayRightRect = new Rect(mOutlineRect.right, mOutlineRect.top, mDrawingRect.right, mOutlineRect.bottom);

        mMatrix = new Matrix();
        mViewWidth = mOutlineRect.width();
        mViewHeight = mOutlineRect.height();

        mBitmapWidth = mBitmap.getWidth();
        mBitmapHeight = mBitmap.getHeight();


        originScale = Math.max(mViewWidth / mBitmapWidth, mViewHeight / mBitmapHeight);
        // 经过缩放以后的长宽
        mCurrentBmWidth = mBitmapWidth * originScale;
        mCurrentBmHeight = mBitmapHeight * originScale;

        originX = mOutlineRectLeft - (mCurrentBmWidth - rectWidth) / 2;
        originY = mOutlineRectTop - (mCurrentBmHeight - rectHeight) / 2;


        mMatrix.setValues(new float[]{originScale, 0, originX,
                0, originScale, originY,
                0, 0, 1f});

        if ((mViewHeight / mBitmapHeight) > 1.0f || (mViewWidth / mBitmapWidth) > 1.0) {
            mMinScale = originScale;
        } else {
            mMinScale = Math.min(mViewWidth / mBitmapWidth, mViewHeight / mBitmapHeight);
        }

        //init value
        mMaxTranslateY = mOutlineRectTop;
        mMinTranslateY = mOutlineRectTop + mViewHeight - mBitmapHeight * originScale;

        mMaxTranslateX = mOutlineRectLeft;
        mMinTranslateX = mDrawingRect.width() - mBitmapWidth * originScale - mOutlineRectLeft;

    }

    public void setHorizontalPadding(int horizontalPadding) {
        if (horizontalPadding >= 0) {
            this.horizontalPadding = horizontalPadding;
        }
    }

    public void setWidthHeightRatio(double widthHeightRatio) {
        if (widthHeightRatio > 1e-6) {
            this.widthHeightRatio = widthHeightRatio;
        }
    }

    public void setImageBitmap(Bitmap bm) {
        mBitmap = bm;
        try {
            ExifInterface exifInterface = new ExifInterface(((ClipImageActivity) mContext).getImagePath());
            int tag = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
            if (tag == ExifInterface.ORIENTATION_ROTATE_90) {
                Matrix m = new Matrix();
                m.setRotate(90, bm.getWidth() / 2, bm.getHeight() / 2);
                mBitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, true);
            } else if (tag == ExifInterface.ORIENTATION_ROTATE_180) {
                Matrix m = new Matrix();
                m.setRotate(180, bm.getWidth() / 2, bm.getHeight() / 2);
                mBitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, true);
            } else if (tag == ExifInterface.ORIENTATION_ROTATE_270) {
                Matrix m = new Matrix();
                m.setRotate(270, bm.getWidth() / 2, bm.getHeight() / 2);
                mBitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, true);
            }
        } catch (Exception e) {
            Log.w("ClipImageView", "error while get image rotation:" + e);
        }
        super.setImageBitmap(bm);
    }

    @Override
    public void onDraw(Canvas canvas) {
        try {
            if (mBitmap == null ||mBitmap.isRecycled()) {
                return;
            }
            if (mMatrix == null) {
                init();
            }
            canvas.drawBitmap(mBitmap, mMatrix, null);
            canvas.drawRect(mGrayTopRect, mGrayPaint);
            canvas.drawRect(mGrayBottomRect, mGrayPaint);
            canvas.drawRect(mGrayLeftRect, mGrayPaint);
            canvas.drawRect(mGrayRightRect, mGrayPaint);
            canvas.drawPath(mOutlinePath, mOutlinePaint);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Deprecated
    public Bitmap clipImage() {

        printMatrix(mMatrix);

        int strokeWidth = (int) mOutlinePaint.getStrokeWidth();

        // 裁剪框的长宽
        int bmWidth = (mOutlineRect.width() - strokeWidth * 2);
        int bmHeight = (mOutlineRect.height() - strokeWidth * 2);

        // 取drawingCache的src部分绘制到新的bitmap的dstRect去
        Rect srcRect = new Rect();
        srcRect.set(mOutlineRect.left + strokeWidth, mOutlineRect.top + strokeWidth,
                mOutlineRect.right - strokeWidth, mOutlineRect.bottom - strokeWidth);
        if(bmHeight<=0||bmWidth<=0){
            return null;
        }

        // 创建一个裁剪框大小的位图
        savedBitmap = Bitmap.createBitmap(bmWidth, bmHeight, Bitmap.Config.ARGB_8888);

        Canvas cv = new Canvas(savedBitmap);
        setDrawingCacheEnabled(false);
        setWillNotCacheDrawing(false);
        setDrawingCacheEnabled(true);

        Rect dstRect = new Rect();
        dstRect.set(0, 0, bmWidth, bmHeight);

        Bitmap drawingCache = getDrawingCache(); // 取drawingCache
        if (drawingCache != null) {
            cv.drawBitmap(drawingCache, srcRect, dstRect, null);
        } else {
            savedBitmap = null;
        }
        return savedBitmap;
    }


    /**
     * @param mImagePath
     * @return
     * NOTICE: 费内存
     */
    public Bitmap clipImage2(String mImagePath) {

        printMatrix(mMatrix);



        float[] values=new float[9];
        mMatrix.getValues(values);

        int x0 = 0;
        int y0 = 0;

        int xStart = (int) (values[0] * x0 + values[1] * y0 + values[2]);
        int yStart = (int) (values[3] * x0 + values[4] * y0 + values[5]);

        int xEnd = (int) (values[0] * mBitmapWidth + values[1] * mBitmapHeight + values[2]);
        int yEnd = (int) (values[3] * mBitmapWidth + values[4] * mBitmapHeight + values[5]);


        int strokeWidth = (int) mOutlinePaint.getStrokeWidth();
        Rect clipRect = new Rect();
        clipRect.set(mOutlineRect.left + strokeWidth, mOutlineRect.top + strokeWidth,
                mOutlineRect.right - strokeWidth, mOutlineRect.bottom - strokeWidth);


        // 换算
        float xStartRatio = (mOutlineRect.left + strokeWidth - xStart) / (float) (xEnd - xStart);
        float xEndRatio = (mOutlineRect.right - strokeWidth - xStart) / (float) (xEnd - xStart);

        float yStartRatio = (mOutlineRect.top + strokeWidth - yStart) / (float) (yEnd - yStart);
        float yEndRatio = (mOutlineRect.bottom - strokeWidth - yStart) / (float) (yEnd - yStart);


        Pair<Integer, Integer> pictureSize = getPictureSizeAdapted(mImagePath);


        int newXStart = (int) (pictureSize.first * xStartRatio);
        int newXEnd = (int) (pictureSize.first * xEndRatio);

        int newYStart = (int) (pictureSize.second * yStartRatio);
        int newYEnd = (int) (pictureSize.second * yEndRatio);



        Rect srcRect = new Rect();
        srcRect.set(newXStart, newYStart, newXEnd, newYEnd);

        int dstWidth = newXEnd - newXStart;
        if (dstWidth <= 0) {
            return null;
        }

        int dstHeight = newYEnd - newYStart;
        if (dstHeight <= 0) {
            return null;
        }

        Rect dstRect = new Rect();
        dstRect.set(0, 0, dstWidth, dstHeight);

        try {
            Bitmap bitmap = BitmapFactory.decodeFile(mImagePath); // oom
            bitmap = ImageUtil.INSTANCE.rotateBitmap(mImagePath, bitmap);
            savedBitmap = Bitmap.createBitmap(dstWidth, dstHeight, Bitmap.Config.ARGB_8888);
            Canvas cv = new Canvas(savedBitmap);
            cv.drawBitmap(bitmap, srcRect, dstRect, null);
            return savedBitmap;
        } catch (OutOfMemoryError e) {
            e.printStackTrace();
            Log.e(TAG, "clipImage2 OOM!");
        }

        return null;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //如果正在动画 则不对事件进行处理;
        if (isAnimating) {
            return true;
        }
        if (event.getPointerCount() == 1) {
            lastX0 = -1;
            lastX1 = -1;
            moveImage(event);
            lastAction = ACTION_MOVE;
        } else {
            scaleImage(event);
            lastAction = ACTION_SCALE;
        }
        return true;
    }

    private static int ACTION_NONE = 0;
    private static int ACTION_MOVE = 1;
    private static int ACTION_SCALE = 2;
    private int primaryPointerId;
    // lastAction作用: 记录上一次的操作是平移还是缩放
    private int lastAction = ACTION_NONE;
    private float lastX;
    private float lastY;

    private void moveImage(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = event.getX();
                lastY = event.getY();
                primaryPointerId = event.getPointerId(0);
                break;

            case MotionEvent.ACTION_MOVE:
                float x = event.getX();
                float y = event.getY();
                // 在检测到只有一个触点时，如果上一次操作是缩放，那么不进行平移
                // (因为这个触点可能距离上一次只有一个触点的坐标很远了，平移会出现图片跳跃的情况)
                if (event.getPointerId(0) == primaryPointerId // the same finger
                        && lastAction != ACTION_SCALE
                        && mMatrix != null) {
                    float[] matrixValues = new float[9];
                    mMatrix.getValues(matrixValues);
                    matrixValues[2] += x - lastX;
                    matrixValues[5] += y - lastY;
                    mMatrix.setValues(matrixValues);
                }
                if (primaryPointerId != event.getPointerId(0)) {
                    primaryPointerId = event.getPointerId(0);
                }
                lastX = x;
                lastY = y;
                this.invalidate();
                break;
            case MotionEvent.ACTION_UP:
                adjustScreen();
                break;
        }
    }


    private void scaleImage(MotionEvent event) {
        float scale = 0f;
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                float x0 = event.getX(0);
                float y0 = event.getY(0);
                float x1 = event.getX(1);
                float y1 = event.getY(1);
                if (lastX0 < 0 || lastX1 < 0) {
                    lastX0 = x0;
                    lastY0 = y0;
                    lastX1 = x1;
                    lastY1 = y1;
                }
                originMidX = 0;
                originMidY = 0;
                scale = (float) (Math.hypot(x1 - x0, y1 - y0) / Math.hypot(lastX1 - lastX0, lastY1 - lastY0));
                float originPoints[] = new float[]{lastX0, lastY0, lastX1, lastY1};

                if (mMatrix != null) {
                    Matrix reverseMatrix = new Matrix();
                    if (mMatrix.invert(reverseMatrix)) {
                        reverseMatrix.mapPoints(originPoints);
                        originMidX = (originPoints[0] + originPoints[2]) / 2;
                        originMidY = (originPoints[1] + originPoints[3]) / 2;
                    }

                    float[] matrixValues = new float[9];
                    mMatrix.getValues(matrixValues);
                    //限制缩放界限
                    if (matrixValues[0] * scale <= mMinScale) {
                        return;
                    }
                    matrixValues[0] *= scale;
                    matrixValues[4] *= scale;

                    matrixValues[2] += (1 - scale) * originMidX * matrixValues[0];
                    matrixValues[5] += (1 - scale) * originMidY * matrixValues[4];
                    resetMinValue(matrixValues[0]);
                    mMatrix.setValues(matrixValues);
                }
                lastX0 = x0;
                lastY0 = y0;
                lastX1 = x1;
                lastY1 = y1;
                this.invalidate();
                break;
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_CANCEL:
                lastX0 = -1;
                lastX1 = -1;
                adjustScreen();
        }
    }

    private void adjustScreen() {
        boolean hasChanged = false;
        final float[] matrixValues = new float[9];
        if (mBitmap == null ||mBitmap.isRecycled()) {
            return;
        }
        if (mMatrix == null) {
            init();
        }
        mMatrix.getValues(matrixValues);
        float animateStartTransX = matrixValues[Matrix.MTRANS_X];
        float animateEndTransX = matrixValues[Matrix.MTRANS_X];
        float animateStartTransY = matrixValues[Matrix.MTRANS_Y];
        float animateEndTransY = matrixValues[Matrix.MTRANS_Y];

        if (matrixValues[Matrix.MTRANS_X] >= mMaxTranslateX) {
            animateEndTransX = mMaxTranslateX;
            hasChanged = true;
        } else if (matrixValues[Matrix.MTRANS_X] <= mMinTranslateX) {
            animateEndTransX = mMinTranslateX;
            hasChanged = true;
        }
        if (matrixValues[Matrix.MTRANS_Y] >= mMaxTranslateY) {
            animateEndTransY = mMaxTranslateY;
            hasChanged = true;
        } else if (matrixValues[Matrix.MTRANS_Y] <= mMinTranslateY) {
            animateEndTransY = mMinTranslateY;
            hasChanged = true;
        }

        if (hasChanged) {
            hasChanged = false;
            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.setDuration(200);
            animatorSet.setInterpolator(new LinearInterpolator());
            animatorSet.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    super.onAnimationStart(animation);
                    isAnimating = true;
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    isAnimating = false;
                }
            });
            ValueAnimator transXAnimator = ValueAnimator.ofFloat(animateStartTransX, animateEndTransX);
            transXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float transX = (Float) animation.getAnimatedValue();
                    matrixValues[Matrix.MTRANS_X] = transX;
                    mMatrix.setValues(matrixValues);
                    ClipImageView.this.invalidate();
                }
            });
            ValueAnimator transYAnimator = ValueAnimator.ofFloat(animateStartTransY, animateEndTransY);
            transYAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float transY = (Float) animation.getAnimatedValue();
                    matrixValues[Matrix.MTRANS_Y] = transY;
                    mMatrix.setValues(matrixValues);
                    ClipImageView.this.invalidate();
                }
            });
            animatorSet.play(transXAnimator).with(transYAnimator);
            animatorSet.start();
        }
    }

    /**
     * 随着缩放的移动 更新着最小值;
     */
    private void resetMinValue(float currentScale) {
        mMinTranslateY = mOutlineRectTop + mViewHeight - mBitmapHeight * currentScale;

        mMinTranslateX = mDrawingRect.width() - mBitmapWidth * currentScale - mOutlineRectLeft;
    }


    public void printMatrix(Matrix matrix){
        float[] values=new float[9];
        matrix.getValues(values);

        for(int i=0; i<3; i++){
            StringBuffer buffer=new StringBuffer();
            for(int j=0; j<3; j++){
                buffer.append(values[i*3+j]+"  ");
            }

        }
    }


    public static Pair<Integer, Integer> getPictureSizeAdapted(String path) {
        if (path == null || path.isEmpty()) {
            return new Pair<>(0, 0);
        }

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        int width = options.outWidth;
        int height = options.outHeight;

        int rotatedWidth = width;
        int rotatedHeight = height;

        try {
            ExifInterface exif = new ExifInterface(path);
            int orientation = exif.getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL
            );

            if (orientation == ExifInterface.ORIENTATION_ROTATE_90 ||
                    orientation == ExifInterface.ORIENTATION_ROTATE_270) {
                // 如果旋转了 90 或 270 度，宽高需要调换
                rotatedWidth = height;
                rotatedHeight = width;
            }

        } catch (IOException e) {
            e.printStackTrace(); // 出错时默认返回原始宽高
        }

        return new Pair<>(rotatedWidth, rotatedHeight);
    }
}

