package net.pokeranalytics.android.ui.fragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm
import kotlinx.android.synthetic.main.fragment_stats.*
import kotlinx.coroutines.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.model.StatRepresentable
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.ui.adapter.DisplayDescriptor
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.SessionObserverFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.util.NULL_TEXT
import timber.log.Timber
import java.util.*
import kotlin.coroutines.CoroutineContext

class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope {

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main

    private var rowRepresentables: ArrayList<RowRepresentable> = ArrayList()
	private var stringAll = ""
	private var stringCashGame = ""
	private var stringTournament = ""

    private lateinit var statsAdapter: RowRepresentableAdapter

    companion object {

        /**
         * Create new instance
         */
        fun newInstance(): StatsFragment {
            val fragment = StatsFragment()
            val bundle = Bundle()
            fragment.arguments = bundle
            return fragment
        }
    }

    // Life Cycle

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_stats, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initData()
	    initUI()
	    launchStatComputation()
    }

    // Row Representable DS

    override fun adapterRows(): List<RowRepresentable>? {
        return this.rowRepresentables
    }

    override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? {
        val dc = DisplayDescriptor()
        dc.textFormat = TextFormat(NULL_TEXT)
        if (row is StatRepresentable) {
            context?.let { context ->
                row.computedStat?.let {
                    dc.textFormat = it.format(context)
                }
            }
        }
        return dc
    }

    override fun statFormatForRow(row: RowRepresentable): TextFormat {
        if (row is StatRepresentable) {
            context?.let { context ->
                row.computedStat?.let { return it.format(context) }
            }
        }
        return TextFormat(NULL_TEXT)
    }

    override fun onResume() {
        super.onResume()
        statsAdapter.notifyDataSetChanged()
    }

    // Override

    override fun sessionsChanged() {
        this.launchStatComputation()
        this.statsAdapter.notifyDataSetChanged()
    }

    // Business

    /**
     * Init data
     */
    private fun initData() {

	    this.stringAll = getString(R.string.all)
	    this.stringCashGame = getString(R.string.cash_game)
	    this.stringTournament = getString(R.string.tournament)

        this.statsAdapter = RowRepresentableAdapter(this)
    }

	/**
	 * Init UI
	 */
	private fun initUI() {

		val viewManager = LinearLayoutManager(requireContext())

		recyclerView.apply {
			setHasFixedSize(true)
			layoutManager = viewManager
			adapter = statsAdapter
		}
	}


    private fun launchStatComputation() {

	    GlobalScope.launch(coroutineContext) {

		    var results = listOf<ComputedResults>()
		    val test = GlobalScope.async {
			    val s = Date()
			    Timber.d(">>> start...")

			    val realm = Realm.getDefaultInstance()
			    results = createSessionGroupsAndStartCompute(realm)
			    realm.close()

			    val e = Date()
			    val duration = (e.time - s.time) / 1000.0
			    Timber.d(">>> ended in ${duration} seconds")

		    }
		    test.await()

		    if (!isDetached) {
			    showResults(results)
		    }
	    }

    }

	private fun createSessionGroupsAndStartCompute(realm: Realm) : List<ComputedResults> {

//        val cgSessions = mutableListOf<Computable>()
//        val tSessions = mutableListOf<Computable>()

		val s = Date()

		val allSessions = realm.where(ComputableResult::class.java).isNotNull("session.endDate").findAll()
		val allSessionSets = realm.where(SessionSet::class.java).findAll()
		Timber.d(">>>>> number of computables to compute = ${allSessions.size}")

		val cgSessions = allSessions.where().equalTo("session.type", Session.Type.CASH_GAME.ordinal).findAll()
		val cgSessionSets = realm.where(SessionSet::class.java).equalTo("sessions.type", Session.Type.CASH_GAME.ordinal).findAll()

		val tSessions = allSessions.where().equalTo("session.type", Session.Type.TOURNAMENT.ordinal).findAll()
		val tSessionSets = realm.where(SessionSet::class.java).equalTo("sessions.type", Session.Type.TOURNAMENT.ordinal).findAll()

		val e = Date()
		val duration = (e.time - s.time) / 1000.0
		Timber.d(">>> filtering in ${duration} seconds")

        val allStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE, Stat.NUMBER_OF_SETS, Stat.AVERAGE_DURATION, Stat.DURATION)
        val allSessionGroup = ComputableGroup(getString(R.string.all), allSessions, allSessionSets, allStats)
        val cgStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.NET_BB_PER_100_HANDS, Stat.HOURLY_RATE_BB, Stat.AVERAGE, Stat.STANDARD_DEVIATION_HOURLY, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN)
        val cgSessionGroup = ComputableGroup(getString(R.string.cash_game), cgSessions, cgSessionSets, cgStats)
        val tStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN)
        val tSessionGroup = ComputableGroup(getString(R.string.tournament), tSessions, tSessionSets, tStats)

        Timber.d(">>>>> Start computations...")

        return Calculator.computeGroups(listOf(allSessionGroup, cgSessionGroup, tSessionGroup), Calculator.Options())

    }

    private fun showResults(results: List<ComputedResults>) {
        this.rowRepresentables = this.convertResultsIntoRepresentables(results)
		statsAdapter.notifyDataSetChanged()
    }

    private fun convertResultsIntoRepresentables(results: List<ComputedResults>) : ArrayList<RowRepresentable> {

        val rows: ArrayList<RowRepresentable> = ArrayList()

        results.forEach { result ->
            rows.add(CustomizableRowRepresentable(title = result.group.name))
            result.group.stats?.forEach { stat ->
                rows.add(StatRepresentable(stat, result.computedStat(stat)))
            }
        }

        return rows
    }

}