package tech.linjiang.pandora.inspector;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Toast;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import tech.linjiang.pandora.inspector.canvas.ClickInfoCanvas;
import tech.linjiang.pandora.inspector.canvas.GridCanvas;
import tech.linjiang.pandora.inspector.canvas.RelativeCanvas;
import tech.linjiang.pandora.inspector.canvas.SelectCanvas;
import tech.linjiang.pandora.inspector.model.Element;
import tech.linjiang.pandora.util.ViewKnife;

/**
 * Created by linjiang on 11/06/2018.
 */
public class OperableView extends ElementHoldView {

    private static final String TAG = "OperableView";

    public OperableView(Context context) {
        super(context);
        ViewConfiguration vc = ViewConfiguration.get(context);
        touchSlop = vc.getScaledTouchSlop();
        longPressTimeout = ViewConfiguration.getLongPressTimeout();
        tapTimeout = ViewConfiguration.getTapTimeout();
        selectCanvas = new SelectCanvas(this);
        relativeCanvas = new RelativeCanvas(this);
        gridCanvas = new GridCanvas(this);
        clickInfoCanvas = new ClickInfoCanvas(this);
    }

    private int searchCount = 0;
    // max selectable count
    private final int elementsNum = 2;
    private Element[] relativeElements = new Element[elementsNum];
    // the latest selected Element when tap.
    private Element targetElement;
    private SelectCanvas selectCanvas;
    private RelativeCanvas relativeCanvas;
    private GridCanvas gridCanvas;
    private ClickInfoCanvas clickInfoCanvas;
    private int touchSlop;
    private long longPressTimeout, tapTimeout;
    private float lastX, lastY;
    // (x, y) when DOWN
    private float downX, downY;
    @State
    private int state;
    private float alpha;
    // anim for indicating longPress action
    private ValueAnimator gridAnimator;

    private final Paint defPaint = new Paint(Paint.ANTI_ALIAS_FLAG) {{
        setColor(Color.YELLOW);
        setStrokeWidth(ViewKnife.dip2px(2));
        setStyle(Style.STROKE);
    }};


    @IntDef({
            State.NONE,
            State.TOUCHING,
            State.PRESSING,
            State.DRAGGING,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface State {
        int NONE = 0x00;
        int PRESSING = 0x01;    // after tapTimeout and before longPressTimeout
        int TOUCHING = 0x02;    // trigger move before dragging
        int DRAGGING = 0x03;    // since long press
    }

    @Override
    public boolean onTouchEvent(final MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = lastX = event.getX();
                downY = lastY = event.getY();
                tryStartCheckTask();
                return true;
            case MotionEvent.ACTION_MOVE:
                if (state == State.DRAGGING) {
                    if (targetElement != null) {
                        float dx = event.getX() - lastX;
                        float dy = event.getY() - lastY;
                        targetElement.offset(dx, dy);
                        for (Element e : relativeElements) {
                            if (e != null) {
                                e.reset();
                            }
                        }
                        invalidate();
                    }
                } else if (state == State.TOUCHING) {
                    // do nothing
                } else {
                    float dx = event.getX() - downX;
                    float dy = event.getY() - downY;
                    if (dx * dx + dy * dy > touchSlop * touchSlop) {
                        if (state == State.PRESSING) {
                            Toast.makeText(getContext(), "CANCEL", Toast.LENGTH_SHORT).show();
                        }
                        state = State.TOUCHING;
                        cancelCheckTask();
                        invalidate();
                        Log.w(TAG, "onTouchEvent: change to State.TOUCHING");
                    }
                }
                lastX = event.getX();
                lastY = event.getY();
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                cancelCheckTask();
                if (state == State.NONE) {
                    handleClick(event.getX(), event.getY());
                } else if (state == State.DRAGGING) {
                    resetAll();
                }
                state = State.NONE;
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), defPaint);
        if (state == State.DRAGGING) {
            gridCanvas.draw(canvas, 1);
        } else if (state == State.PRESSING) {
            gridCanvas.draw(canvas, alpha);
        }
        selectCanvas.draw(canvas, relativeElements);
        relativeCanvas.draw(canvas, relativeElements[searchCount % elementsNum],
                relativeElements[Math.abs(searchCount - 1) % elementsNum]);

        clickInfoCanvas.draw(canvas);
    }


    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        cancelCheckTask();
        relativeElements = null;
    }

    private void cancelCheckTask() {
        removeCallbacks(longPressCheck);
        removeCallbacks(tapTimeoutCheck);
        if (gridAnimator != null) {
            gridAnimator.cancel();
            gridAnimator = null;
        }
    }

    private void tryStartCheckTask() {
        cancelCheckTask();
        if (targetElement != null) {
            postDelayed(longPressCheck, longPressTimeout);
            postDelayed(tapTimeoutCheck, tapTimeout);
        }
    }

    private Runnable longPressCheck = new Runnable() {
        @Override
        public void run() {
            state = State.DRAGGING;
            alpha = 1;
        }
    };
    private Runnable tapTimeoutCheck = new Runnable() {
        @Override
        public void run() {
            state = State.PRESSING;
            gridAnimator = ObjectAnimator.ofFloat(0, 1)
                    .setDuration(longPressTimeout - tapTimeout);
            gridAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float value = (float) animation.getAnimatedValue();
                    alpha = value;
                    invalidate();
                }
            });
            gridAnimator.start();
        }
    };


    private void handleClick(float x, float y) {
        final Element element = getTargetElement(x, y);
        handleElementSelected(element, true);
    }

    public boolean handleClick(View v) {
        final Element element = getTargetElement(v);
        handleElementSelected(element, false);
        invalidate();
        return element != null;
    }

    private void handleElementSelected(Element element, boolean cancelIfSelected) {
        targetElement = element;
        if (element != null) {
            boolean bothNull = true;
            for (int i = 0; i < relativeElements.length; i++) {
                if (relativeElements[i] != null) {
                    if (relativeElements[i] == element) {
                        if (cancelIfSelected) {
                            // cancel selected
                            relativeElements[i] = null;
                            searchCount = i;
                        }
                        if (clickListener != null) {
                            clickListener.onClick(element.getView());
                        }
                        return;
                    }
                    bothNull = false;
                }
            }
            if (bothNull) {
                // If only one is selected, show info
                clickInfoCanvas.setInfoElement(element);
            }
            relativeElements[searchCount % elementsNum] = element;
            searchCount++;
            if (clickListener != null) {
                clickListener.onClick(element.getView());
            }
        }
    }

    public boolean isSelectedEmpty() {
        boolean empty = true;
        for (int i = 0; i < elementsNum; i++) {
            if (relativeElements[i] != null) {
                empty = false;
                break;
            }
        }
        return empty;
    }

    private OnClickListener clickListener;
    @Override
    public void setOnClickListener(@Nullable OnClickListener l) {
        clickListener = l;
    }

}