package com.gh.common.exposure

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.LayoutManager
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.feature.exposure.ExposureEvent
import io.reactivex.functions.Consumer

/**
 * Exposure Event Listener for RecyclerView
 */
class ExposureListener(var fragment: Fragment, var exposable: IExposable) : RecyclerView.OnScrollListener() {

    val throttleBus: ExposureThrottleBus by lazy {
        ExposureThrottleBus(
            { commitExposure(it) },
            Consumer(Throwable::printStackTrace),
            { commitExternalExposure(it) }
        )
    }
    var layoutManager: LayoutManager? = null
    var visibleState: ExposureThrottleBus.VisibleState? = null

    init {
        fragment.fragmentManager?.registerFragmentLifecycleCallbacks(
            object : FragmentManager.FragmentLifecycleCallbacks() {
                override fun onFragmentPaused(fm: FragmentManager, f: Fragment) {
                    if (fragment == f) {
                        visibleState?.let { commitExposure(it) }
                        visibleState?.let { commitExternalExposure(it) }
                        throttleBus.clear()
                    }
                }

                override fun onFragmentViewDestroyed(fm: FragmentManager, f: Fragment) {
                    if (fragment == f) {
                        fragment.fragmentManager?.unregisterFragmentLifecycleCallbacks(this)
                    }
                }
            }, false
        )
    }

    /**
     * Monitor items in display when scrolling, record those newly displayed as well as
     * those newly disappeared. And finally trigger commitExposure().
     */
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        layoutManager = recyclerView.layoutManager

        if (layoutManager != null) {
            if (layoutManager is LinearLayoutManager) {
                (layoutManager as LinearLayoutManager).run {
                    visibleState =
                        ExposureThrottleBus.VisibleState(findFirstVisibleItemPosition(), findLastVisibleItemPosition())
                    throttleBus.postVisibleState(visibleState!!)
                }
            } else if (layoutManager is StaggeredGridLayoutManager) {
                (recyclerView.layoutManager as StaggeredGridLayoutManager).run {
                    val firstVisibleItemArray = IntArray(2)
                    val lastVisibleItemArray = IntArray(2)

                    findFirstVisibleItemPositions(firstVisibleItemArray)
                    findLastVisibleItemPositions(lastVisibleItemArray)

                    visibleState =
                        ExposureThrottleBus.VisibleState(firstVisibleItemArray.first(), lastVisibleItemArray.first())
                    throttleBus.postVisibleState(visibleState!!)
                }
            }
        }
    }

    /**
     * Just commit the exposureEvent that is stored in listItem.
     */
    private fun commitExposure(visibleState: ExposureThrottleBus.VisibleState) {

        val eventList = arrayListOf<ExposureEvent>()

        for (pos in visibleState.firstVisiblePosition..visibleState.lastVisiblePosition) {
            try {
                exposable.getEventByPosition(pos)?.let { eventList.add(it) }
                exposable.getEventListByPosition(pos)?.let { eventList.addAll(it) }
            } catch (ignore: Exception) {
                // Just ignore the error.
            }
        }

        ExposureManager.log(eventList)
    }

    /**
     * 提交第三方的曝光事件 （微信小游戏CPM 和 DSP 游戏）
     * 由于普通曝光事件的上报通道存在节流特性，不符合CPM接口对于数据上报的实时性和准确性的要求，所以使用额外的通道进行上报
     */
    private fun commitExternalExposure(visibleState: ExposureThrottleBus.VisibleState) {

        val cpmEventList = arrayListOf<ExposureEvent>()
        val dspEventList = arrayListOf<ExposureEvent>()

        for (pos in visibleState.firstVisiblePosition..visibleState.lastVisiblePosition) {
            try {
                exposable.getEventByPosition(pos)?.let {
                    when (it.payload.miniGameType) {
                        Constants.WECHAT_MINI_GAME_CPM -> cpmEventList.add(it)
                        Constants.DSP_GAME -> dspEventList.add(it)
                        else -> {
                            // do nothing
                        }
                    }
                }
                exposable.getEventListByPosition(pos)?.let { list ->
                    list.forEach {
                        when (it.payload.miniGameType) {
                            Constants.WECHAT_MINI_GAME_CPM -> cpmEventList.add(it)
                            Constants.DSP_GAME -> dspEventList.add(it)
                            else -> {
                                // do nothing
                            }
                        }
                    }
                }
            } catch (ignore: Exception) {
                // Just ignore the error.
            }
        }
        ExposureManager.logExternal(cpmEventList, dspEventList)
    }


}