package com.termux.view.textselection;

import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.PopupWindow;

import com.termux.R;
import com.termux.view.TerminalView;

@SuppressLint("ViewConstructor")
public class TextSelectionHandleView extends View {
    private final TerminalView terminalView;
    private PopupWindow mHandle;
    private final CursorController mCursorController;

    private final Drawable mHandleLeftDrawable;
    private final Drawable mHandleRightDrawable;
    private Drawable mHandleDrawable;

    private boolean mIsDragging;

    final int[] mTempCoords = new int[2];
    Rect mTempRect;

    private int mPointX;
    private int mPointY;
    private float mTouchToWindowOffsetX;
    private float mTouchToWindowOffsetY;
    private float mHotspotX;
    private float mHotspotY;
    private float mTouchOffsetY;
    private int mLastParentX;
    private int mLastParentY;

    private int mHandleHeight;
    private int mHandleWidth;

    private final int mInitialOrientation;
    private int mOrientation;

    public static final int LEFT = 0;
    public static final int RIGHT = 2;

    private long mLastTime;

    public TextSelectionHandleView(TerminalView terminalView, CursorController cursorController, int initialOrientation) {
        super(terminalView.getContext());
        this.terminalView = terminalView;
        mCursorController = cursorController;
        mInitialOrientation = initialOrientation;

        mHandleLeftDrawable = getContext().getDrawable(R.drawable.text_select_handle_left_material);
        mHandleRightDrawable = getContext().getDrawable(R.drawable.text_select_handle_right_material);

        setOrientation(mInitialOrientation);
    }

    private void initHandle() {
        mHandle = new PopupWindow(terminalView.getContext(), null,
            android.R.attr.textSelectHandleWindowStyle);
        mHandle.setSplitTouchEnabled(true);
        mHandle.setClippingEnabled(false);
        mHandle.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
        mHandle.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
        mHandle.setBackgroundDrawable(null);
        mHandle.setAnimationStyle(0);

        mHandle.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
        mHandle.setEnterTransition(null);
        mHandle.setExitTransition(null);
        mHandle.setContentView(this);
    }

    public void setOrientation(int orientation) {
        mOrientation = orientation;
        int handleWidth = 0;
        switch (orientation) {
            case LEFT: {
                mHandleDrawable = mHandleLeftDrawable;
                handleWidth = mHandleDrawable.getIntrinsicWidth();
                mHotspotX = (handleWidth * 3) / (float) 4;
                break;
            }

            case RIGHT: {
                mHandleDrawable = mHandleRightDrawable;
                handleWidth = mHandleDrawable.getIntrinsicWidth();
                mHotspotX = handleWidth / (float) 4;
                break;
            }
        }

        mHandleHeight = mHandleDrawable.getIntrinsicHeight();

        mHandleWidth = handleWidth;
        mTouchOffsetY = -mHandleHeight * 0.3f;
        mHotspotY = 0;
        invalidate();
    }

    public void show() {
        if (!isPositionVisible()) {
            hide();
            return;
        }

        // We remove handle from its parent first otherwise the following exception may be thrown
        // java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
        removeFromParent();

        initHandle(); // init the handle
        invalidate(); // invalidate to make sure onDraw is called

        final int[] coords = mTempCoords;
        terminalView.getLocationInWindow(coords);
        coords[0] += mPointX;
        coords[1] += mPointY;

        if (mHandle != null)
            mHandle.showAtLocation(terminalView, 0, coords[0], coords[1]);
    }

    public void hide() {
        mIsDragging = false;

        if (mHandle != null) {
            mHandle.dismiss();

            // We remove handle from its parent, otherwise it may still be shown in some cases even after the dismiss call
            removeFromParent();
            mHandle = null;  // garbage collect the handle
        }
        invalidate();
    }

    public void removeFromParent() {
        if (!isParentNull()) {
            ((ViewGroup)this.getParent()).removeView(this);
        }
    }

    public void positionAtCursor(final int cx, final int cy, boolean forceOrientationCheck) {
        int x = terminalView.getPointX(cx);
        int y = terminalView.getPointY(cy + 1);
        moveTo(x, y, forceOrientationCheck);
    }

    private void moveTo(int x, int y, boolean forceOrientationCheck) {
        float oldHotspotX = mHotspotX;
        checkChangedOrientation(x, forceOrientationCheck);
        mPointX = (int) (x - (isShowing() ? oldHotspotX : mHotspotX));
        mPointY = y;

        if (isPositionVisible()) {
            int[] coords = null;

            if (isShowing()) {
                coords = mTempCoords;
                terminalView.getLocationInWindow(coords);
                int x1 = coords[0] + mPointX;
                int y1 = coords[1] + mPointY;
                if (mHandle != null)
                    mHandle.update(x1, y1, getWidth(), getHeight());
            } else {
                show();
            }

            if (mIsDragging) {
                if (coords == null) {
                    coords = mTempCoords;
                    terminalView.getLocationInWindow(coords);
                }
                if (coords[0] != mLastParentX || coords[1] != mLastParentY) {
                    mTouchToWindowOffsetX += coords[0] - mLastParentX;
                    mTouchToWindowOffsetY += coords[1] - mLastParentY;
                    mLastParentX = coords[0];
                    mLastParentY = coords[1];
                }
            }
        } else {
            hide();
        }
    }

    public void changeOrientation(int orientation) {
        if (mOrientation != orientation) {
            setOrientation(orientation);
        }
    }

    private void checkChangedOrientation(int posX, boolean force) {
        if (!mIsDragging && !force) {
            return;
        }
        long millis = SystemClock.currentThreadTimeMillis();
        if (millis - mLastTime < 50 && !force) {
            return;
        }
        mLastTime = millis;

        final TerminalView hostView = terminalView;
        final int left = hostView.getLeft();
        final int right = hostView.getWidth();
        final int top = hostView.getTop();
        final int bottom = hostView.getHeight();

        if (mTempRect == null) {
            mTempRect = new Rect();
        }
        final Rect clip = mTempRect;
        clip.left = left + terminalView.getPaddingLeft();
        clip.top = top + terminalView.getPaddingTop();
        clip.right = right - terminalView.getPaddingRight();
        clip.bottom = bottom - terminalView.getPaddingBottom();

        final ViewParent parent = hostView.getParent();
        if (parent == null || !parent.getChildVisibleRect(hostView, clip, null)) {
            return;
        }

        if (posX - mHandleWidth < clip.left) {
            changeOrientation(RIGHT);
        } else if (posX + mHandleWidth > clip.right) {
            changeOrientation(LEFT);
        } else {
            changeOrientation(mInitialOrientation);
        }
    }

    private boolean isPositionVisible() {
        // Always show a dragging handle.
        if (mIsDragging) {
            return true;
        }

        final TerminalView hostView = terminalView;
        final int left = 0;
        final int right = hostView.getWidth();
        final int top = 0;
        final int bottom = hostView.getHeight();

        if (mTempRect == null) {
            mTempRect = new Rect();
        }
        final Rect clip = mTempRect;
        clip.left = left + terminalView.getPaddingLeft();
        clip.top = top + terminalView.getPaddingTop();
        clip.right = right - terminalView.getPaddingRight();
        clip.bottom = bottom - terminalView.getPaddingBottom();

        final ViewParent parent = hostView.getParent();
        if (parent == null || !parent.getChildVisibleRect(hostView, clip, null)) {
            return false;
        }

        final int[] coords = mTempCoords;
        hostView.getLocationInWindow(coords);
        final int posX = coords[0] + mPointX + (int) mHotspotX;
        final int posY = coords[1] + mPointY + (int) mHotspotY;

        return posX >= clip.left && posX <= clip.right &&
            posY >= clip.top && posY <= clip.bottom;
    }

    @Override
    public void onDraw(Canvas c) {
        final int width = mHandleDrawable.getIntrinsicWidth();
        int height = mHandleDrawable.getIntrinsicHeight();
        mHandleDrawable.setBounds(0, 0, width, height);
        mHandleDrawable.draw(c);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        terminalView.updateFloatingToolbarVisibility(event);
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                final float rawX = event.getRawX();
                final float rawY = event.getRawY();
                mTouchToWindowOffsetX = rawX - mPointX;
                mTouchToWindowOffsetY = rawY - mPointY;
                final int[] coords = mTempCoords;
                terminalView.getLocationInWindow(coords);
                mLastParentX = coords[0];
                mLastParentY = coords[1];
                mIsDragging = true;
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                final float rawX = event.getRawX();
                final float rawY = event.getRawY();

                final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
                final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY + mTouchOffsetY;

                mCursorController.updatePosition(this, Math.round(newPosX), Math.round(newPosY));
                break;
            }

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mIsDragging = false;
        }
        return true;
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(mHandleDrawable.getIntrinsicWidth(),
            mHandleDrawable.getIntrinsicHeight());
    }

    public int getHandleHeight() {
        return mHandleHeight;
    }

    public int getHandleWidth() {
        return mHandleWidth;
    }

    public boolean isShowing() {
        if (mHandle != null)
            return mHandle.isShowing();
        else
            return false;
    }

    public boolean isParentNull() {
        return this.getParent() == null;
    }

    public boolean isDragging() {
        return mIsDragging;
    }

}
