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.filter.QueryCondition
import net.pokeranalytics.android.ui.activity.StatisticDetailsActivity
import net.pokeranalytics.android.ui.adapter.DisplayDescriptor
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
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.ui.view.rowrepresentable.StatRow
import net.pokeranalytics.android.util.NULL_TEXT
import timber.log.Timber
import java.util.*
import kotlin.coroutines.CoroutineContext

class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope,
	RowRepresentableDelegate {

	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
	private var report : Report? = null

    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 StatRow) {
            context?.let { context ->
                row.computedStat?.let {
                    dc.textFormat = it.format(context)
                }
            }
        }
        return dc
    }

    override fun statFormatForRow(row: RowRepresentable): TextFormat {
        if (row is StatRow) {
            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, 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 r = Report()
			val test = GlobalScope.async {
				val s = Date()
				Timber.d(">>> start...")

				val realm = Realm.getDefaultInstance()
				r = createSessionGroupsAndStartCompute(realm)
				report = r
				realm.close()

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

			}
			test.await()

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

	}

	private fun createSessionGroupsAndStartCompute(realm: Realm): Report {

		val allStats: List<Stat> = listOf(
			Stat.NETRESULT,
			Stat.HOURLY_RATE,
			Stat.AVERAGE,
			Stat.NUMBER_OF_SETS,
			Stat.AVERAGE_DURATION,
			Stat.DURATION
		)
		val allSessionGroup = ComputableGroup(stringAll, listOf(), 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(stringCashGame, listOf(QueryCondition.CASH), 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(stringTournament, listOf(QueryCondition.TOURNAMENT), tStats)

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

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

	}

	private fun showResults(report: Report) {
		this.rowRepresentables = this.convertReportIntoRepresentables(report)
		statsAdapter.notifyDataSetChanged()
	}

	private fun convertReportIntoRepresentables(report: Report): ArrayList<RowRepresentable> {

		val rows: ArrayList<RowRepresentable> = ArrayList()

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

		return rows
	}

	// RowRepresentableDelegate

	override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {

		if (row is StatRow && row.stat.hasEvolutionGraph) {

			// filter groups
			val groupResults = this.report?.results?.filter {
				it.group.name == row.groupName
			}

			groupResults?.firstOrNull()?.let {
				this.launchStatComputationWithEvolution(row.stat, it.group)
			}
		}

	}

	private fun launchStatComputationWithEvolution(stat: Stat, computableGroup: ComputableGroup) {

		GlobalScope.launch(coroutineContext) {

			var report = Report()
			val test = GlobalScope.async {
				val s = Date()
				Timber.d(">>> start...")

				val realm = Realm.getDefaultInstance()

				val aggregationType = stat.aggregationTypes.first()
				report = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType)

				realm.close()

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

			}
			test.await()

			if (!isDetached) {
				StatisticDetailsActivity.newInstance(requireContext(), stat, computableGroup, report)
			}
		}

	}

}