package com.adealink.weparty.webview

import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewParent
import android.webkit.WebSettings
import android.webkit.WebView
import android.widget.OverScroller
import androidx.core.view.NestedScrollingChild3
import androidx.core.view.NestedScrollingChildHelper
import androidx.core.view.ViewCompat
import com.adealink.frame.base.AppBase
import com.adealink.frame.log.Log
import com.adealink.frame.util.AppUtil
import com.adealink.weparty.webview.callback.IWebViewCallback
import com.adealink.weparty.webview.constant.TAG_WEB_VIEW
import com.adealink.weparty.webview.jsbridge.JSBridge
import com.adealink.weparty.webview.jsbridge.data.NativeMessage
import com.adealink.weparty.webview.jsbridge.method.JSNativeMethod
import com.adealink.weparty.webview.jsnativemethod.addCommonMethod
import com.adealink.weparty.webview.scheme.registerCommonSchemeHandler
import com.adjust.sdk.webbridge.AdjustBridge
import com.ushareit.easysdk.web.view.SPWebView
import kotlin.math.abs


/**
 */
abstract class BaseWebView : SPWebView, IWebView, NestedScrollingChild3 {

    companion object {
        private const val INVALID_POINTER = -1
    }

    private var lUrl: String? = null

    private var jsBridges = hashMapOf<String, JSBridge>()
    override var webViewCallback: IWebViewCallback? = null

    private var commonWebViewClient: CommonWebViewClient? = null
    private var commonWebChromeClient: CommonWebChromeClient? = null

    private val scrollOffset = IntArray(2)
    private val scrollConsumed = IntArray(2)
    private var lastMotionY = 0F
    private lateinit var childHelper: NestedScrollingChildHelper
    private var isBeingDragged = false
    private var velocityTracker: VelocityTracker? = null
    private var touchSlop = 0
    private var activePointerId = INVALID_POINTER
    private var nestedYOffset = 0F
    private lateinit var scroller: OverScroller
    private var minimumVelocity = 0F
    private var maximumVelocity = 0F
    private var lastScrollerY = 0

    constructor(context: Context) : super(context) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
            super(context, attrs, defStyleAttr) {
        init()
    }

    private fun init() {
        initScrollView()
        initWebViewSetting()
        initJSBridge(createDefaultJSBridge())
        initWebViewClient()
        initWebChromeClient()
    }

    @SuppressLint("SetJavaScriptEnabled")
    open fun initWebViewSetting() {
        settings.domStorageEnabled = true
        settings.javaScriptEnabled = true
        settings.useWideViewPort = true
        settings.defaultTextEncodingName = "utf-8"
        settings.cacheMode = WebSettings.LOAD_DEFAULT
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //5.0以上开启混合模式加载
            settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
        }
        if (!AppBase.isRelease && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            setWebContentsDebuggingEnabled(true)
        }
//        settings.setUserAgentString(sb) 增加UA
        settings.builtInZoomControls = false
        settings.setSupportZoom(false)
        settings.saveFormData = false
        settings.savePassword = false
        settings.allowFileAccess = true
        settings.useWideViewPort = true
        settings.loadWithOverviewMode = true
        settings.javaScriptCanOpenWindowsAutomatically = true
        settings.mediaPlaybackRequiresUserGesture = false
    }

    abstract fun createDefaultJSBridge(): JSBridge

    @SuppressLint("JavascriptInterface")
    protected fun initJSBridge(bridge: JSBridge) {
        Log.d(
            TAG_WEB_VIEW,
            "initJSBridge, jsBridge:$bridge, interfaceName:${bridge.interfaceName()}"
        )
        addJSBridge(bridge)
    }

    open fun addJSNativeMethods(jsBridge: JSBridge) {}

    open fun addJSNativeMethod(method: JSNativeMethod<*, *>) {
        for (bridge in jsBridges.values) {
            bridge.addNativeMethod(method)
        }
    }

    @SuppressLint("JavascriptInterface")
    fun addJSBridge(bridge: JSBridge) {
        jsBridges[bridge.interfaceName()] = bridge
        Log.d(
            TAG_WEB_VIEW,
            "addJSBridge, interfaceName:${bridge.interfaceName()}, jsBridge:$bridge"
        )
        addJavascriptInterface(bridge as Any, bridge.interfaceName())
        bridge.addCommonMethod(this)
        addJSNativeMethods(bridge)
    }

    fun getJSBridge(name: String): JSBridge? {
        return jsBridges[name]
    }

    open fun <T> notifyNativeMessage(message: NativeMessage<T>) {
        for (bridge in jsBridges.values) {
            bridge.notifyNativeMessage(message)
        }
    }

    override fun shouldReloadUrl(url: String): Boolean {
        return false
    }

    open fun initWebChromeClient() {
        commonWebChromeClient = CommonWebChromeClient(this)
        webChromeClient = commonWebChromeClient
    }

    open fun initWebViewClient() {
        commonWebViewClient = CommonWebViewClient(this)
        commonWebViewClient?.registerCommonSchemeHandler(this)
        val constWebViewClient = commonWebViewClient ?: return
        webViewClient = constWebViewClient
    }

    override fun getCommonWebChromeClient(): CommonWebChromeClient? {
        return commonWebChromeClient
    }

    override fun getCommonWebViewClient(): CommonWebViewClient? {
        return commonWebViewClient
    }

    override fun loadUrl(url: String) {
        lUrl = url
        webViewCallback?.beforeLoadUrl(url)
        super.loadUrl(url)
    }

    override fun getLoadUrl(): String? {
        return lUrl
    }

    override fun getWebView(): WebView {
        return this
    }

    override fun clearCache(includeDiskFiles: Boolean) {
        super.clearCache(includeDiskFiles)
        try {
            context.deleteDatabase("webview.db")
            context.deleteDatabase("webviewCache.db")
        } catch (e: Exception) {
            Log.e(TAG_WEB_VIEW, "clearCache, e:${e}")
        }
    }

    open fun initScrollView() {
        childHelper = NestedScrollingChildHelper(this)
        overScrollMode = WebView.OVER_SCROLL_NEVER
        isNestedScrollingEnabled = true
        scroller = OverScroller(context)
        val configuration: ViewConfiguration = ViewConfiguration.get(context)
        touchSlop = configuration.scaledTouchSlop
        minimumVelocity = configuration.scaledMinimumFlingVelocity.toFloat()
        maximumVelocity = configuration.scaledMaximumFlingVelocity.toFloat()
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        val action: Int = ev.action
        if (action == MotionEvent.ACTION_MOVE && isBeingDragged) {
            return true
        }

        when (action and MotionEvent.ACTION_MASK) {
            MotionEvent.ACTION_MOVE -> {
                val activePointerId = activePointerId
                if (activePointerId == INVALID_POINTER) {
                    return isBeingDragged
                }

                val pointerIndex: Int = ev.findPointerIndex(activePointerId)
                if (pointerIndex == -1) {
                    Log.e(
                        TAG_WEB_VIEW, "Invalid pointerId:$activePointerId in onInterceptTouchEvent"
                    )
                    return isBeingDragged
                }
                val y = ev.getY(pointerIndex)
                val yDiff = abs(y - lastMotionY)
                if (yDiff > touchSlop && nestedScrollAxes and ViewCompat.SCROLL_AXIS_VERTICAL === 0) {
                    isBeingDragged = true
                    lastMotionY = y
                    initVelocityTrackerIfNotExists()
                    velocityTracker?.addMovement(ev)
                    nestedYOffset = 0F
                    val parent: ViewParent? = parent
                    parent?.requestDisallowInterceptTouchEvent(true)
                }
            }

            MotionEvent.ACTION_DOWN -> {
                lastMotionY = ev.y
                activePointerId = ev.getPointerId(0)
                initOrResetVelocityTracker()
                velocityTracker?.addMovement(ev)
                scroller.computeScrollOffset()
                isBeingDragged = !scroller.isFinished
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
            }

            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
                isBeingDragged = false
                activePointerId = INVALID_POINTER
                recycleVelocityTracker()
                if (scroller.springBack(scrollX, scrollY, 0, 0, 0, getScrollRange())) {
                    ViewCompat.postInvalidateOnAnimation(this)
                }
                stopNestedScroll()
            }

            MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
        }
        return isBeingDragged
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(ev: MotionEvent): Boolean {
        initVelocityTrackerIfNotExists()
        val vtev: MotionEvent = MotionEvent.obtain(ev)
        val actionMasked: Int = ev.actionMasked
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            nestedYOffset = 0F
        }
        vtev.offsetLocation(0F, nestedYOffset)
        when (actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                if (!scroller.isFinished.also { isBeingDragged = it }) {
                    parent?.requestDisallowInterceptTouchEvent(true)
                }
                if (!scroller.isFinished) {
                    abortAnimatedScroll()
                }
                lastMotionY = ev.y
                activePointerId = ev.getPointerId(0)
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH)
            }

            MotionEvent.ACTION_MOVE -> {
                val activePointerIndex: Int = ev.findPointerIndex(activePointerId)
                if (activePointerIndex == -1) {
                    Log.e(TAG_WEB_VIEW, "Invalid pointerId:$activePointerId in onTouchEvent")
                    velocityTracker?.addMovement(vtev)
                    vtev.recycle()
                    return super.onTouchEvent(ev)
                }
                val y = ev.getY(activePointerIndex)
                var deltaY = lastMotionY - y
                if (dispatchNestedPreScroll(
                        0,
                        deltaY.toInt(),
                        scrollConsumed,
                        scrollOffset,
                        ViewCompat.TYPE_TOUCH
                    )
                ) {
                    deltaY -= scrollConsumed[1]
                    nestedYOffset += scrollOffset[1]
                }
                if (!isBeingDragged && abs(deltaY) > touchSlop) {
                    this.parent?.requestDisallowInterceptTouchEvent(true)
                    isBeingDragged = true
                    if (deltaY > 0) {
                        deltaY -= touchSlop
                    } else {
                        deltaY += touchSlop
                    }
                }
                if (isBeingDragged) {
                    lastMotionY = y - scrollOffset[1]
                    val oldY = scrollY
                    val range = getScrollRange()
                    // Calling overScrollByCompat will call onOverScrolled, which
                    // calls onScrollChanged if applicable.
                    if (overScrollByCompat(
                            0, deltaY.toInt(), 0, oldY, 0, range, 0,
                            0, true
                        ) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)
                    ) {
                        velocityTracker?.clear()
                    }
                    val scrolledDeltaY = scrollY - oldY
                    val unconsumedY = deltaY - scrolledDeltaY
                    scrollConsumed[1] = 0
                    dispatchNestedScroll(
                        0, scrolledDeltaY, 0, unconsumedY.toInt(), scrollOffset,
                        ViewCompat.TYPE_TOUCH, scrollConsumed
                    )
                    lastMotionY -= scrollOffset[1]
                    nestedYOffset += scrollOffset[1]
                }
            }

            MotionEvent.ACTION_UP -> {
                velocityTracker?.computeCurrentVelocity(1000, maximumVelocity)
                val initialVelocity = velocityTracker?.getYVelocity(activePointerId) ?: 0F
                if (abs(initialVelocity) > minimumVelocity) {
                    if (!dispatchNestedPreFling(0f, -initialVelocity)) {
                        dispatchNestedFling(0f, -initialVelocity, true)
                        fling(-initialVelocity.toInt())
                    }
                } else if (scroller.springBack(scrollX, scrollY, 0, 0, 0, getScrollRange())) {
                    ViewCompat.postInvalidateOnAnimation(this)
                }
                activePointerId = INVALID_POINTER
                endDrag()
            }

            MotionEvent.ACTION_CANCEL -> {
                if (isBeingDragged) {
                    if (scroller.springBack(scrollX, scrollY, 0, 0, 0, getScrollRange())) {
                        ViewCompat.postInvalidateOnAnimation(this)
                    }
                }
                activePointerId = INVALID_POINTER
                endDrag()
            }

            MotionEvent.ACTION_POINTER_DOWN -> {
                val index: Int = ev.actionIndex
                lastMotionY = ev.getY(index)
                activePointerId = ev.getPointerId(index)
            }

            MotionEvent.ACTION_POINTER_UP -> {
                onSecondaryPointerUp(ev)
                lastMotionY = ev.getY(ev.findPointerIndex(activePointerId))
            }
        }
        velocityTracker?.addMovement(vtev)
        vtev.recycle()
        return super.onTouchEvent(ev)
    }

    open fun abortAnimatedScroll() {
        scroller.abortAnimation()
        stopNestedScroll(ViewCompat.TYPE_NON_TOUCH)
    }

    open fun endDrag() {
        isBeingDragged = false
        recycleVelocityTracker()
        stopNestedScroll()
    }

    open fun onSecondaryPointerUp(ev: MotionEvent) {
        val pointerIndex: Int =
            (ev.action and MotionEvent.ACTION_POINTER_INDEX_MASK shr MotionEvent.ACTION_POINTER_INDEX_SHIFT)
        val pointerId: Int = ev.getPointerId(pointerIndex)
        if (pointerId == activePointerId) {
            val newPointerIndex = if (pointerIndex == 0) 1 else 0
            lastMotionY = ev.getY(newPointerIndex)
            activePointerId = ev.getPointerId(newPointerIndex)
            velocityTracker?.clear()
        }
    }

    open fun fling(velocityY: Int) {
        val height = height
        scroller.fling(
            scrollX, scrollY,
            0, velocityY,
            0, 0, Int.MIN_VALUE, Int.MAX_VALUE,
            0, height / 2
        )
        runAnimatedScroll(true)
    }

    open fun runAnimatedScroll(participateInNestedScrolling: Boolean) {
        if (participateInNestedScrolling) {
            startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH)
        } else {
            stopNestedScroll(ViewCompat.TYPE_NON_TOUCH)
        }
        lastScrollerY = scrollY
        ViewCompat.postInvalidateOnAnimation(this)
    }

    override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
        if (disallowIntercept) {
            recycleVelocityTracker()
        }
        super.requestDisallowInterceptTouchEvent(disallowIntercept)
    }

    open fun initOrResetVelocityTracker() {
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain()
        } else {
            velocityTracker?.clear()
        }
    }

    open fun initVelocityTrackerIfNotExists() {
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain()
        }
    }

    open fun recycleVelocityTracker() {
        velocityTracker?.recycle()
        velocityTracker = null
    }

    override fun overScrollBy(
        deltaX: Int, deltaY: Int,
        scrollX: Int, scrollY: Int,
        scrollRangeX: Int, scrollRangeY: Int,
        maxOverScrollX: Int, maxOverScrollY: Int,
        isTouchEvent: Boolean
    ): Boolean {
        if (!isBeingDragged) overScrollByCompat(
            deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
            maxOverScrollX, maxOverScrollY, isTouchEvent
        )
        return true
    }

    open fun getScrollRange(): Int {
        return computeVerticalScrollRange()
    }

    override fun isNestedScrollingEnabled(): Boolean {
        return childHelper.isNestedScrollingEnabled
    }

    override fun setNestedScrollingEnabled(enabled: Boolean) {
        childHelper.isNestedScrollingEnabled = enabled
    }

    override fun startNestedScroll(axes: Int, type: Int): Boolean {
        return childHelper.startNestedScroll(axes, type)
    }

    override fun startNestedScroll(axes: Int): Boolean {
        return startNestedScroll(axes, ViewCompat.TYPE_TOUCH)
    }

    override fun stopNestedScroll(type: Int) {
        childHelper.stopNestedScroll(type)
    }

    override fun stopNestedScroll() {
        stopNestedScroll(ViewCompat.TYPE_TOUCH)
    }

    override fun hasNestedScrollingParent(type: Int): Boolean {
        return childHelper.hasNestedScrollingParent(type)
    }

    override fun hasNestedScrollingParent(): Boolean {
        return hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)
    }

    override fun dispatchNestedScroll(
        dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int,
        offsetInWindow: IntArray?
    ): Boolean {
        return dispatchNestedScroll(
            dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
            offsetInWindow, ViewCompat.TYPE_TOUCH
        )
    }

    override fun dispatchNestedScroll(
        dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int,
        offsetInWindow: IntArray?, type: Int
    ): Boolean {
        return childHelper.dispatchNestedScroll(
            dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
            offsetInWindow, type
        )
    }

    override fun dispatchNestedScroll(
        dxConsumed: Int,
        dyConsumed: Int,
        dxUnconsumed: Int,
        dyUnconsumed: Int,
        offsetInWindow: IntArray?,
        type: Int,
        consumed: IntArray
    ) {
        childHelper.dispatchNestedScroll(
            dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
            offsetInWindow, type, consumed
        )
    }

    override fun dispatchNestedPreScroll(
        dx: Int,
        dy: Int,
        consumed: IntArray?,
        offsetInWindow: IntArray?
    ): Boolean {
        return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, ViewCompat.TYPE_TOUCH)
    }

    override fun dispatchNestedPreScroll(
        dx: Int,
        dy: Int,
        consumed: IntArray?,
        offsetInWindow: IntArray?,
        type: Int
    ): Boolean {
        return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
    }

    override fun dispatchNestedFling(
        velocityX: Float,
        velocityY: Float,
        consumed: Boolean
    ): Boolean {
        return childHelper.dispatchNestedFling(velocityX, velocityY, false)
    }

    override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean {
        return childHelper.dispatchNestedPreFling(velocityX, velocityY)
    }

    override fun getNestedScrollAxes(): Int {
        return ViewCompat.SCROLL_AXIS_VERTICAL
    }

    override fun computeScroll() {
        if (scroller.isFinished) {
            return
        }

        scroller.computeScrollOffset()
        val y: Int = scroller.currY
        var unconsumed = y - lastScrollerY
        lastScrollerY = y

        // Nested Scrolling Pre Pass
        scrollConsumed[1] = 0
        dispatchNestedPreScroll(
            0, unconsumed, scrollConsumed, null,
            ViewCompat.TYPE_NON_TOUCH
        )
        unconsumed -= scrollConsumed[1]
        if (unconsumed != 0) {
            // Internal Scroll
            val oldScrollY = scrollY
            overScrollByCompat(
                0, unconsumed, scrollX, oldScrollY, 0, getScrollRange(),
                0, 0, false
            )
            val scrolledByMe = scrollY - oldScrollY
            unconsumed -= scrolledByMe

            // Nested Scrolling Post Pass
            scrollConsumed[1] = 0
            dispatchNestedScroll(
                0, 0, 0, unconsumed, scrollOffset,
                ViewCompat.TYPE_NON_TOUCH, scrollConsumed
            )
            unconsumed -= scrollConsumed[1]
        }
        if (unconsumed != 0) {
            abortAnimatedScroll()
        }
        if (!scroller.isFinished) {
            ViewCompat.postInvalidateOnAnimation(this)
        }
    }

    // copied from NestedScrollView exacly as it looks, leaving overscroll related code, maybe future use
    open fun overScrollByCompat(
        deltaX: Int, deltaY: Int,
        scrollX: Int, scrollY: Int,
        scrollRangeX: Int, scrollRangeY: Int,
        maxOverScrollX: Int, maxOverScrollY: Int,
        isTouchEvent: Boolean
    ): Boolean {
        var maxOverScrollX = maxOverScrollX
        var maxOverScrollY = maxOverScrollY
        val overScrollMode = overScrollMode
        val canScrollHorizontal = computeHorizontalScrollRange() > computeHorizontalScrollExtent()
        val canScrollVertical = computeVerticalScrollRange() > computeVerticalScrollExtent()
        val overScrollHorizontal = (overScrollMode == View.OVER_SCROLL_ALWAYS
                || overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal)
        val overScrollVertical = (overScrollMode == View.OVER_SCROLL_ALWAYS
                || overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical)
        var newScrollX = scrollX + deltaX
        if (!overScrollHorizontal) {
            maxOverScrollX = 0
        }
        var newScrollY = scrollY + deltaY
        if (!overScrollVertical) {
            maxOverScrollY = 0
        }

        // Clamp values if at the limits and record
        val left = -maxOverScrollX
        val right = maxOverScrollX + scrollRangeX
        val top = -maxOverScrollY
        val bottom = maxOverScrollY + scrollRangeY
        var clampedX = false
        if (newScrollX > right) {
            newScrollX = right
            clampedX = true
        } else if (newScrollX < left) {
            newScrollX = left
            clampedX = true
        }
        var clampedY = false
        if (newScrollY > bottom) {
            newScrollY = bottom
            clampedY = true
        } else if (newScrollY < top) {
            newScrollY = top
            clampedY = true
        }
        if (clampedY && !hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) {
            scroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange())
        }
        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY)
        return clampedX || clampedY
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        commonWebViewClient?.onPageBack(this, originalUrl)
    }

}