package com.adealink.weparty.commonui.widget.floatview.view

import android.animation.ValueAnimator
import android.graphics.PixelFormat
import android.graphics.Point
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.WindowManager
import android.view.animation.LinearInterpolator
import com.adealink.frame.log.Log
import com.adealink.frame.util.DisplayUtil.getScreenHeight
import com.adealink.frame.util.DisplayUtil.getScreenWidth
import com.adealink.frame.util.isTouchPointInView
import com.adealink.weparty.commonui.widget.floatview.data.IWindowFloatData
import com.adealink.weparty.commonui.widget.floatview.data.MODE_APPLICATION
import com.adealink.weparty.commonui.widget.floatview.data.MODE_SYSTEM
import com.adealink.weparty.commonui.widget.floatview.data.TAG_FLOAT_DRAG_VIEW
import com.adealink.weparty.commonui.widget.floatview.data.TAG_FLOAT_VIEW
import java.lang.Math.abs


abstract class BaseDragFloatView(baseFloatData: IWindowFloatData) :
    BaseWindowFloatView(baseFloatData) {

    companion object {
        private const val TAG = "BaseDragFloatView"
        const val LOCATION_FIXED_NONE = 0
        const val LOCATION_FIXED_LEFT = 1 shl 0
        const val LOCATION_FIXED_RIGHT = 1 shl 1
        const val LOCATION_FIXED_TOP = 1 shl 2
        const val LOCATION_FIXED_BOTTOM = 1 shl 3
    }

    private val touchSlop = ViewConfiguration.get(context).scaledPagingTouchSlop
    private val downPoint = Point()
    private val lastPoint = Point()
    private val movePoint = Point()
    private val maxMovePoint = Point()
    var maxX = getScreenWidth() - getLayoutParamWidth()
    var maxY = getScreenHeight() - getLayoutParamHeight()

    var toLeftAnim: ValueAnimator? = null
    var toRightAnim: ValueAnimator? = null

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                downPoint.x = event.rawX.toInt()
                downPoint.y = event.rawY.toInt()
                lastPoint.x = downPoint.x
                lastPoint.y = downPoint.y
            }

            MotionEvent.ACTION_MOVE -> {
                movePoint.x = event.rawX.toInt()
                movePoint.y = event.rawY.toInt()
                updateViewLayout(movePoint.x - lastPoint.x, movePoint.y - lastPoint.y)
                maxMovePoint.x = Math.max(lastPoint.x, movePoint.x)
                maxMovePoint.y = Math.max(lastPoint.y, movePoint.y)
                lastPoint.x = movePoint.x
                lastPoint.y = movePoint.y
            }

            MotionEvent.ACTION_UP -> {
                if ((abs(maxMovePoint.x - downPoint.x) <= touchSlop
                            && abs(maxMovePoint.y - downPoint.y) <= touchSlop)
                    || lastPoint.x == downPoint.x && lastPoint.y == downPoint.y
                ) {
                    val clickableViews = getClickableViews()
                    clickableViews?.forEach {
                        if (isTouchPointInView(it, event.rawX.toInt(), event.rawY.toInt())) {
                            it.performClick()
                            return true
                        }
                    }
                    performClick()
                } else {
                    if (applySnapToEdge()) {
                        moveToLocation()
                    }
                }
            }

            else -> {

            }
        }
        return true
    }

    private fun moveToLocation() {

        val fixedLocation = getFixedLocation()
        //horizontal
        if ((fixedLocation and LOCATION_FIXED_LEFT) != 0) {
            moveToLeftWithAnim()
        } else if ((fixedLocation and LOCATION_FIXED_RIGHT) != 0) {
            moveToRightWithAnim()
        }
        //vertical
        if ((fixedLocation and LOCATION_FIXED_TOP) != 0) {
            moveToTop()
        } else if ((fixedLocation and LOCATION_FIXED_BOTTOM) != 0) {
            moveToBottom()
        }

    }

    private fun updateViewLayout(changeX: Int, changeY: Int) {
        val newX = changeX + windowParams.x
        val newY = changeY + windowParams.y
        if (newX in 0 until maxX) {
            windowParams.x = newX
        }
        if (newY in 0 until maxY) {
            windowParams.y = newY
        }
        updateViewLayout()
    }


    private fun moveToRightWithAnim() {
        val location = IntArray(2)
        getLocationOnScreen(location)
        val dx = location[0]
        toRightAnim = ValueAnimator.ofInt(dx, maxX).apply {
            addUpdateListener {
                val data = it.animatedValue as Int
                windowParams.x = data
                updateViewLayout()
            }
            interpolator = LinearInterpolator()
            duration = 100
        }
        toRightAnim?.start()
    }


    private fun moveToLeftWithAnim() {
        val location = IntArray(2)
        getLocationOnScreen(location)
        val dx = location[0]
        toLeftAnim = ValueAnimator.ofInt(dx, 0).apply {
            addUpdateListener {
                val data = it.animatedValue as Int
                windowParams.x = data
                updateViewLayout()
            }
            interpolator = LinearInterpolator()
            duration = 100
        }
        toLeftAnim?.start()
    }

    private fun moveToTop() {
        var dy = lastPoint.y
        while (dy > 0) {
            updateViewLayout(0, -1)
            dy--
        }
    }

    private fun moveToBottom() {
        var dy = getScreenHeight() - lastPoint.y
        while (dy > 0) {
            updateViewLayout(0, 1)
            dy--
        }
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        return true
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        setMeasuredDimension(
            getDefaultSize(0, widthMeasureSpec),
            getDefaultSize(0, heightMeasureSpec)
        )
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

    override val windowParams = WindowManager.LayoutParams().apply {
        width = getLayoutParamWidth()
        height = getLayoutParamHeight()
        format = PixelFormat.TRANSLUCENT
        flags = (WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                //or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
        gravity = Gravity.START or Gravity.TOP
        val layoutX = getLayoutParamX()
        val layoutY = getLayoutParamY()
        if (layoutX != null && layoutY != null) {
            x = layoutX
            y = layoutY
        } else {
            x =
                if (getFixedLocation() == LOCATION_FIXED_LEFT) 0 else getScreenWidth() - width
            y =
                if (getFixedLocation() == LOCATION_FIXED_TOP) 0 else getScreenHeight() - height
        }
    }

    private fun updateViewLayout() {
        when (baseFloatData.windowMode()) {
            MODE_APPLICATION -> {
                if (windowParams.token != null) {
                    try {
                        windowManager?.getCurrentWindowManager()
                            ?.updateViewLayout(this@BaseDragFloatView, windowParams)
                    } catch (e: Throwable) {
                        Log.e(
                            TAG_FLOAT_VIEW,
                            "$TAG_FLOAT_DRAG_VIEW, updateViewLayout crash, $e"
                        )
                    }
                } else {
                    Log.w(
                        TAG_FLOAT_VIEW,
                        "$TAG_FLOAT_DRAG_VIEW, updateViewLayout fail(${this}), windowParams.token is null"
                    )
                }
            }

            MODE_SYSTEM -> {
                try {
                    windowManager?.getCurrentWindowManager()
                        ?.updateViewLayout(this@BaseDragFloatView, windowParams)
                } catch (e: Throwable) {
                    Log.e(
                        TAG_FLOAT_VIEW,
                        "$TAG_FLOAT_DRAG_VIEW, updateViewLayout crash(${this}), $e"
                    )
                }
            }
        }
    }

    abstract fun getLayoutParamWidth(): Int
    abstract fun getLayoutParamHeight(): Int
    abstract fun getClickableViews(): List<View>?
    abstract fun getLayoutParamX(): Int?
    abstract fun getLayoutParamY(): Int?

    /**
     * 悬浮view，控制是否吸边效果
     */
    abstract fun applySnapToEdge(): Boolean

    protected open fun getFixedLocation(): Int {
        val location = IntArray(2)
        getLocationOnScreen(location)
        return if (context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
            if (location[0] + getLayoutParamWidth() / 2 <= getScreenWidth() / 2) {
                LOCATION_FIXED_LEFT
            } else {
                LOCATION_FIXED_RIGHT
            }
        } else {
            if (location[0] + getLayoutParamWidth() / 2 <= getScreenWidth() / 2) {
                LOCATION_FIXED_LEFT
            } else {
                LOCATION_FIXED_RIGHT
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        toLeftAnim?.cancel()
        toLeftAnim = null
        toRightAnim?.cancel()
        toRightAnim = null
    }
}