/*
 * MIT License
 *
 * Copyright (c) 2017 K Sun <[email protected]>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.jcodeing.anchorimageview.widget;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import com.jcodeing.anchorimageview.Anchor;

import java.util.TreeMap;

public class AnchorImageView extends ImageView implements View.OnTouchListener {

    public int parentWidth;
    public int parentHeight;
    public float widthRatio;
    public float heightRatio;

    private Paint clickableStroke;
    private Paint clickFill;
    private Paint clickStroke;
    private Paint warnFill;
    private Paint warnStroke;

    private RectF rectAnchor;
    private RectF rectClickAnchor;
    public int drawRoundRectRadius;

    private Anchor anchor;
    private Anchor anchorPrevious;
    private TreeMap<Integer, Anchor> anchors;

    public boolean isShowClickableAnchor;
    public boolean isAnchorCutProcess;
    private boolean isWarnAnchor;

    private GestureDetector gestureDetector;


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

    public AnchorImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AnchorImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        drawRoundRectRadius = dpToPx(5);
        setScaleType(ImageView.ScaleType.FIT_XY);

        clickableStroke = new Paint(Paint.ANTI_ALIAS_FLAG);
        clickableStroke.setStrokeWidth(1f);
        clickableStroke.setStyle(Paint.Style.STROKE);
        clickableStroke.setStrokeCap(Paint.Cap.ROUND);
        clickableStroke.setColor(Color.BLUE);

        clickFill = new Paint(Paint.ANTI_ALIAS_FLAG);
        clickFill.setStyle(Paint.Style.FILL_AND_STROKE);
        clickFill.setStrokeCap(Paint.Cap.ROUND);
        clickFill.setColor(Color.BLUE);
        clickFill.setAlpha(70);

        clickStroke = new Paint(Paint.ANTI_ALIAS_FLAG);
        clickStroke.setStyle(Paint.Style.STROKE);
        clickStroke.setColor(Color.BLUE);
        clickStroke.setStrokeWidth(2f);
        clickStroke.setStrokeCap(Paint.Cap.ROUND);

        warnFill = new Paint(Paint.ANTI_ALIAS_FLAG);
        warnFill.setStyle(Paint.Style.FILL_AND_STROKE);
        warnFill.setStrokeCap(Paint.Cap.ROUND);
        warnFill.setColor(Color.YELLOW);
        warnFill.setAlpha(70);

        warnStroke = new Paint(Paint.ANTI_ALIAS_FLAG);
        warnStroke.setStyle(Paint.Style.STROKE);
        warnStroke.setColor(Color.YELLOW);
        warnStroke.setStrokeWidth(2f);
        warnStroke.setStrokeCap(Paint.Cap.ROUND);

        setOnTouchListener(this);
        gestureDetector = new GestureDetector(getContext(), new OnGestureListenerAnchor(this));
    }

    @Override
    protected void onDetachedFromWindow() {
        this.resetData();
        super.onDetachedFromWindow();
    }

    public void resetData() {
        isShowClickableAnchor = false;
        anchors = null;
        anchor = null;
        anchorPrevious = null;
        if (rectClickAnchor != null)
            rectClickAnchor.setEmpty();

        if (rectAnchor != null)
            rectAnchor.setEmpty();

        onDrawAnchorListener = null;

        postInvalidate();
        isAnchorCutProcess = false;
    }

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

        if (isShowClickableAnchor || onDrawAnchorListener != null) {
            if (rectAnchor == null)
                rectAnchor = new RectF();
            if (anchors != null) {
                for (int key : anchors.keySet()) {
                    Anchor anchor = anchors.get(key);
                    rectAnchor.set((float) anchor.left * widthRatio, (float) anchor.top * heightRatio, (float) anchor
                            .right * widthRatio, (float) anchor.bottom * heightRatio);
                    if (isShowClickableAnchor)
                        if (rectClickAnchor == null || !rectClickAnchor.contains(rectAnchor)) {
                            if (isWarnAnchor) {
                                canvas.drawRoundRect(rectAnchor, (float) drawRoundRectRadius, (float) drawRoundRectRadius, warnFill);
                                canvas.drawRoundRect(rectAnchor, (float) drawRoundRectRadius, (float) drawRoundRectRadius, warnStroke);
                            } else {
                                canvas.drawRoundRect(rectAnchor, (float) drawRoundRectRadius, (float) drawRoundRectRadius, clickableStroke);
                            }
                        }
                    if (onDrawAnchorListener != null)
                        onDrawAnchorListener.onDrawAnchor(anchor, rectAnchor, canvas);
                }
            }
        }

        if (rectClickAnchor != null && !rectClickAnchor.isEmpty()) {
            if (rectClickAnchor != null) {
                canvas.drawRoundRect(rectClickAnchor, ((float) drawRoundRectRadius), ((float) drawRoundRectRadius), this.clickFill);
                canvas.drawRoundRect(rectClickAnchor, ((float) drawRoundRectRadius), ((float) drawRoundRectRadius), this.clickStroke);
            }
        }

        canvas.restore();
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
    }

    protected void disposeEvent(MotionEvent e) {
        if (anchors == null || anchors.size() == 0) {
            Toast.makeText(getContext(), "No Anchor", Toast.LENGTH_LONG).show();
        } else {
            float x = e.getX();
            float y = e.getY();
            if (anchors != null) {
                for (Integer key : anchors.keySet()) {
                    Anchor anchor = anchors.get(key);
                    if (new RectF(widthRatio * (float) anchor.left, heightRatio * (float) anchor.top,
                            widthRatio * (float) anchor.right, heightRatio * (float) anchor.bottom).contains(x, y)) {
                        // =========@Bingo@=========
                        this.anchor = anchor;

                        //clean WarnAnchor
                        if (isWarnAnchor) {
                            isWarnAnchor = false;
                            postInvalidate();
                        }
                        //on listener
                        if (onAnchorClickListener != null)
                            onAnchorClickListener.onAnchorClick(anchor, anchor.id, widthRatio, heightRatio);

                        return;
                    }
                }
                // =========@Cry@=========

                //current click show -> return
                /*if (rectClickAnchor != null && !rectClickAnchor.isEmpty())
                    return;*/

                //mark WarnAnchor
                if (isWarnAnchor)
                    return;
                isWarnAnchor = true;

                postInvalidate();
            }
        }
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        calculateWidthHeightRatio();
    }

    // ============================@Set/Get@============================
    public void setParentWidthHeight(int width, int height) {
        parentWidth = width;
        parentHeight = height;
    }

    public void calculateWidthHeightRatio() {
        widthRatio = (float) parentWidth / getDrawable().getIntrinsicWidth();
        heightRatio = (float) parentHeight / getDrawable().getIntrinsicHeight();
    }

    public void setCurrentClickAnchor(Anchor anchor) {
        if (anchor != null) {
            this.anchor = anchor;
            runAnchorCutProcess();
        }
    }

    public void setDefaultImageResId(int defaultImageResId) {
        setScaleType(ImageView.ScaleType.CENTER);
        setImageResource(defaultImageResId);
    }

    public void setIShowClickableAnchor(boolean iShowClickableAnchor) {
        this.isShowClickableAnchor = iShowClickableAnchor;
        postInvalidate();
    }

    public void setAnchors(TreeMap<Integer, Anchor> anchors) {
        this.anchors = anchors;
    }

    public int getAnchorSize() {
        return anchors == null ? 0 : anchors.size();
    }

    public TreeMap<Integer, Anchor> getAnchors() {
        return anchors;
    }

    public void cleanCurrentClickAnchor() {
        if (rectClickAnchor != null)
            rectClickAnchor.setEmpty();

        isWarnAnchor = false;
        postInvalidate();
    }


    // ============================@OnAnchorClickListener@============================
    public interface OnDrawAnchorListener {
        void onDrawAnchor(Anchor anchor, RectF rectAnchor, Canvas canvas);
    }

    private OnDrawAnchorListener onDrawAnchorListener;

    public void setOnDrawAnchorListener(OnDrawAnchorListener onDrawAnchorListener) {
        this.onDrawAnchorListener = onDrawAnchorListener;
    }

    // ============================@OnAnchorClickListener@============================
    public interface OnAnchorClickListener {
        void onAnchorClick(Anchor anchor, int id, float widthRatio, float heightRatio);
    }

    private OnAnchorClickListener onAnchorClickListener;

    public void setOnAnchorClickListener(OnAnchorClickListener onAnchorClickListener) {
        this.onAnchorClickListener = onAnchorClickListener;
    }


    // ============================@AnchorCutProcess@============================
    private AnchorCutProcess anchorCutProcess;

    private void stopAnchorCutProcess() {
        isAnchorCutProcess = false;
        if (rectClickAnchor != null)
            rectClickAnchor.setEmpty();

        postInvalidate();
    }

    private void runAnchorCutProcess() {
        if (anchorCutProcess != null) {
            anchorCutProcess.interrupt();
        }

        anchorCutProcess = new AnchorCutProcess(this);
        anchorCutProcess.start();
    }

    class AnchorCutProcess extends Thread {
        /**
         * difference
         */
        private final float leftD;
        private final float rightD;
        private final float topD;
        private final float bottomD;
        /**
         * difference move element
         */
        private final float leftDMovElement;
        private final float rightDMovElement;
        private final float topDMovElement;
        private final float bottomDMovElement;
        /**
         * Previous
         */
        private float leftP;
        private float topP;
        private float rightP;
        private float bottomP;
        /**
         * diffMovElementTotal:200
         * diffMovElementMultiple:10
         * diffMovElementMultipleIncrement:+=10 | 10~200 | RunTotal:20
         */
        private int diffMovElementMultipleIncrement;

        private AnchorImageView anchorImageView;

        AnchorCutProcess(AnchorImageView anchorImageView) {
            super();
            float diffMovElementTotal = 200f;
            this.anchorImageView = anchorImageView;

            isAnchorCutProcess = true;
            // =========@AnchorCutProcess Start@=========

            if (rectClickAnchor == null)
                rectClickAnchor = new RectF();

            if (anchorPrevious != null) {
                leftP = (float) anchorPrevious.left;
                rightP = (float) anchorPrevious.right;
                topP = (float) anchorPrevious.top;
                bottomP = (float) anchorPrevious.bottom;
            }

            leftD = (float) anchor.left - leftP;
            rightD = (float) anchor.right - rightP;
            topD = (float) anchor.top - topP;
            bottomD = (float) anchor.bottom - bottomP;

            rectClickAnchor.set(leftP * widthRatio, topP * heightRatio, rightP * widthRatio, bottomP * heightRatio);

            leftDMovElement = leftD / diffMovElementTotal;
            rightDMovElement = rightD / diffMovElementTotal;
            topDMovElement = topD / diffMovElementTotal;
            bottomDMovElement = bottomD / diffMovElementTotal;
        }

        public void run() {
            while (isAnchorCutProcess) {
                if (anchor == null)
                    return;

                diffMovElementMultipleIncrement += 10;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    anchorImageView.stopAnchorCutProcess();
                    return;
                }

                float leftR = leftP + leftDMovElement * (float) diffMovElementMultipleIncrement;
                float rightR = rightP + rightDMovElement * (float) diffMovElementMultipleIncrement;
                float topR = topP + topDMovElement * (float) diffMovElementMultipleIncrement;
                float bottomR = bottomP + bottomDMovElement * (float) diffMovElementMultipleIncrement;
                try {
                    if (Math.abs(leftR - (float) anchor.left) <= 2f)
                        leftR = (float) anchor.left;

                    if (Math.abs(rightR - (float) anchor.right) <= 2f)
                        rightR = (float) anchor.right;

                    if (Math.abs(topR - (float) anchor.top) <= 2f)
                        topR = (float) anchor.top;

                    if (Math.abs(bottomR - (float) anchor.bottom) <= 2f)
                        bottomR = (float) anchor.bottom;

                    rectClickAnchor.set(widthRatio * leftR, heightRatio * topR, widthRatio * rightR, heightRatio * bottomR);
                    anchorImageView.postInvalidate();

                    if (anchor == null) {
                        continue;
                    }

                    if (leftR != (float) anchor.left || topR != (float) anchor.top || rightR != (float) anchor.right || bottomR != (float) anchor.bottom)
                        continue;

                    isAnchorCutProcess = false;
                    // =========@AnchorCutProcess End@=========
                    anchorPrevious = anchor;
                    anchor = null;
                    return;
                } catch (Exception e) {
                    anchorImageView.stopAnchorCutProcess();
                }
            }
        }
    }


    // ------------------------------K------------------------------@Assist
    public int dpToPx(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }
}