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 com.google.android.material.tabs.TabLayout
import io.realm.Realm
import io.realm.RealmModel
import kotlinx.android.synthetic.main.fragment_calendar.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.ui.activity.CalendarDetailsActivity
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.extensions.hideWithAnimation
import net.pokeranalytics.android.ui.extensions.showWithAnimation
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.view.CalendarTabs
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.util.extensions.*
import timber.log.Timber
import java.util.*
import kotlin.coroutines.CoroutineContext


class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentableDataSource,
	RowRepresentableDelegate {

	enum class TimeFilter {
		MONTH, YEAR
	}

	companion object {

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

	private lateinit var calendarAdapter: RowRepresentableAdapter

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


	private var rows: ArrayList<CustomizableRowRepresentable> = ArrayList()
	private var sortedMonthlyReports: SortedMap<Date, ComputedResults> = HashMap<Date, ComputedResults>().toSortedMap()
	private var sortedYearlyReports: SortedMap<Date, ComputedResults> = HashMap<Date, ComputedResults>().toSortedMap()
	private var datesForRows: HashMap<CustomizableRowRepresentable, Date> = HashMap()

	private var sessionTypeCondition: QueryCondition? = null
	private var currentTimeFilter: TimeFilter = TimeFilter.MONTH
	private var currentStat = Stat.NET_RESULT

	// Life Cycle

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

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

		launchStatComputation()
	}

	override fun adapterRows(): List<RowRepresentable>? {
		return rows
	}

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

		when (currentTimeFilter) {
			TimeFilter.MONTH -> {
				val date = datesForRows[row]
				sortedMonthlyReports[datesForRows[row]]?.let {
					CalendarDetailsActivity.newInstance(
						requireContext(),
						it,
						sessionTypeCondition,
						date?.getMonthAndYear()
					)
				}
			}
			TimeFilter.YEAR -> {
				val date = datesForRows[row]
				sortedYearlyReports[datesForRows[row]]?.let {
					CalendarDetailsActivity.newInstance(requireContext(), it, sessionTypeCondition, date?.getDateYear())
				}
			}
		}
	}

	override val observedEntities: List<Class<out RealmModel>> = listOf(ComputableResult::class.java)

	override fun entitiesChanged(clazz: Class<out RealmModel>) {
		launchStatComputation()
	}

	// Business

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

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

		CalendarTabs.values().forEach {
			val tab = tabs.newTab()
			tab.text = getString(it.resId)
			tabs.addTab(tab)
		}

		tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
			override fun onTabSelected(tab: TabLayout.Tab) {
				when (tab.position) {
					0 -> currentStat = Stat.NET_RESULT
					1 -> currentStat = Stat.HOURLY_RATE
					2 -> currentStat = Stat.NUMBER_OF_GAMES
					3 -> currentStat = Stat.WIN_RATIO
					4 -> currentStat = Stat.STANDARD_DEVIATION_HOURLY
					5 -> currentStat = Stat.AVERAGE
					6 -> currentStat = Stat.AVERAGE_HOURLY_DURATION
					7 -> currentStat = Stat.HOURLY_DURATION
				}
				displayData()
			}

			override fun onTabUnselected(tab: TabLayout.Tab) {
			}

			override fun onTabReselected(tab: TabLayout.Tab) {
			}
		})

		// Manage session type queryWith
		filterSessionAll.setOnCheckedChangeListener { _, isChecked ->
			if (isChecked) {
				sessionTypeCondition = null
				filterSessionCash.isChecked = false
				filterSessionTournament.isChecked = false
				launchStatComputation()
			} else if (sessionTypeCondition == null) {
				filterSessionAll.isChecked = true
			}
		}
		filterSessionCash.setOnCheckedChangeListener { _, isChecked ->
			if (isChecked) {
				sessionTypeCondition = QueryCondition.IsCash
				filterSessionAll.isChecked = false
				filterSessionTournament.isChecked = false
				launchStatComputation()
			} else if (sessionTypeCondition == QueryCondition.IsCash) {
				filterSessionCash.isChecked = true
			}
		}
		filterSessionTournament.setOnCheckedChangeListener { _, isChecked ->
			if (isChecked) {
				sessionTypeCondition = QueryCondition.IsTournament
				filterSessionAll.isChecked = false
				filterSessionCash.isChecked = false
				launchStatComputation()
			} else if (sessionTypeCondition == QueryCondition.IsTournament) {
				filterSessionTournament.isChecked = true
			}
		}

		// Manage time queryWith
		filterTimeMonth.setOnCheckedChangeListener { _, isChecked ->
			if (isChecked) {
				currentTimeFilter = TimeFilter.MONTH
				filterTimeYear.isChecked = false
				displayData()
			} else if (currentTimeFilter == TimeFilter.MONTH) {
				filterTimeMonth.isChecked = true
			}

		}
		filterTimeYear.setOnCheckedChangeListener { _, isChecked ->
			if (isChecked) {
				currentTimeFilter = TimeFilter.YEAR
				filterTimeMonth.isChecked = false
				displayData()
			} else if (currentTimeFilter == TimeFilter.YEAR) {
				filterTimeYear.isChecked = true
			}
		}

		val viewManager = LinearLayoutManager(requireContext())

		calendarAdapter = RowRepresentableAdapter(this, this)

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


	/**
	 * Launch stat computation
	 */
	private fun launchStatComputation() {

		progressBar?.showWithAnimation()
		recyclerView?.hideWithAnimation()

		GlobalScope.launch {

			val calendar = Calendar.getInstance()
			calendar.time = Date().startOfMonth()

			val startDate = Date()
			val realm = Realm.getDefaultInstance()

			val monthlyReports: HashMap<Date, ComputedResults> = HashMap()
			val yearlyReports: HashMap<Date, ComputedResults> = HashMap()

			val requiredStats: List<Stat> =
				listOf(Stat.LOCATIONS_PLAYED, Stat.LONGEST_STREAKS, Stat.DAYS_PLAYED, Stat.STANDARD_DEVIATION_HOURLY)

			// Compute data per AnyYear and AnyMonthOfYear

//            println(">>>> ${Criteria.MonthsOfYear.queryConditions.map { it.id }}")

			val monthlyQueries = when (sessionTypeCondition) {
				QueryCondition.IsCash -> listOf(Criteria.AllMonthsUpToNow, Criteria.Cash).combined()
				QueryCondition.IsTournament -> listOf(Criteria.AllMonthsUpToNow, Criteria.Tournament).combined()
				else -> listOf(Criteria.Years, Criteria.MonthsOfYear).combined()
			}

			monthlyQueries.forEach { query ->
				val options = Calculator.Options(
					progressValues = Calculator.Options.ProgressValues.STANDARD,
					stats = requiredStats,
					query = query
				)
				val report = Calculator.computeStats(realm, options = options)
				report.results.forEach { computedResults ->
					if (!computedResults.isEmpty) {
						// Set date data
						query.conditions.forEach { condition ->
							when (condition) {
								is QueryCondition.AnyYear -> calendar.set(Calendar.YEAR, condition.listOfValues.first())
								is QueryCondition.AnyMonthOfYear -> calendar.set(
									Calendar.MONTH,
									condition.listOfValues.first()
								)
							}
						}

						monthlyReports[calendar.time] = computedResults
					}
				}
			}

			calendar.time = Date().startOfYear()

			// Compute data per AnyYear
			val yearConditions = when (sessionTypeCondition) {
				QueryCondition.IsCash -> listOf(Criteria.Years, Criteria.Cash).combined()
				QueryCondition.IsTournament -> listOf(Criteria.Years, Criteria.Tournament).combined()
				else -> listOf(Criteria.Years).combined()
			}

			yearConditions.forEach { query ->
				val options = Calculator.Options(
					progressValues = Calculator.Options.ProgressValues.STANDARD,
					stats = requiredStats,
					query = query
				)
				val report = Calculator.computeStats(realm, options = options)
				report.results.forEach { computedResults ->
					if (!computedResults.isEmpty) {
						// Set date data
						query.conditions.forEach { condition ->
							when (condition) {
								is QueryCondition.AnyYear -> calendar.set(Calendar.YEAR, condition.listOfValues.first())
							}
						}
						yearlyReports[calendar.time] = computedResults
					}
				}
			}

			sortedMonthlyReports = monthlyReports.toSortedMap(compareByDescending { it })
			sortedYearlyReports = yearlyReports.toSortedMap(compareByDescending { it })

			Timber.d("Computation: ${System.currentTimeMillis() - startDate.time}ms")

			// Logs
			/*
			Timber.d("========== AnyYear x AnyMonthOfYear")
			sortedMonthlyReports.keys.forEach {
				Timber.d("$it => ${sortedMonthlyReports[it]?.computedStat(Stat.NET_RESULT)?.value}")
			}

			Timber.d("========== YEARLY")
			sortedYearlyReports.keys.forEach {
				Timber.d("$it => ${sortedYearlyReports[it]?.computedStat(Stat.NET_RESULT)?.value}")
			}
			*/

			realm.close()

			GlobalScope.launch(Dispatchers.Main) {
				displayData()
			}
		}
	}

	/**
	 * Display data
	 */
	private fun displayData() {

		val startDate = Date()

		datesForRows.clear()
		rows.clear()

		when (currentTimeFilter) {

			// Create monthly reports
			TimeFilter.MONTH -> {
				val years: ArrayList<String> = ArrayList()
				sortedMonthlyReports.keys.forEach { date ->
					if (!years.contains(date.getDateYear())) {
						years.add(date.getDateYear())
						rows.add(
							CustomizableRowRepresentable(
								customViewType = RowViewType.HEADER_TITLE,
								title = date.getDateYear()
							)
						)
					}

					sortedMonthlyReports[date]?.computedStat(currentStat)?.let { computedStat ->

						val row = CustomizableRowRepresentable(
							customViewType = RowViewType.TITLE_VALUE_ARROW,
							title = date.getDateMonth(),
							computedStat = computedStat,
							isSelectable = true
						)

						rows.add(row)
						datesForRows.put(row, date)
					}
				}
			}

			// Create yearly reports
			TimeFilter.YEAR -> {
				sortedYearlyReports.keys.forEach { date ->
					sortedYearlyReports[date]?.computedStat(currentStat)?.let { computedStat ->
						val row = CustomizableRowRepresentable(
							customViewType = RowViewType.TITLE_VALUE_ARROW,
							title = date.getDateYear(),
							computedStat = computedStat,
							isSelectable = true
						)

						rows.add(row)
						datesForRows.put(row, date)
					}
				}
			}
		}

		Timber.d("Display data: ${System.currentTimeMillis() - startDate.time}ms")
		Timber.d("Rows: ${rows.size}")

		calendarAdapter.notifyDataSetChanged()

		progressBar?.hideWithAnimation()
		recyclerView?.showWithAnimation()

	}

}