package net.pokeranalytics.android.model.utils

import io.realm.RealmQuery
import io.realm.RealmResults
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import kotlin.math.max

class CorruptSessionSetException(message: String) : Exception(message)

/**
 * The manager is in charge of updating the abstract concept of timeline,
 * representing the sequenced time frames where the user plays.
 */
class SessionSetManager {

	companion object {

		/**
		 * Updates the global timeline using the updated [session]
		 */
		fun updateTimeline(session: Session) {

			if (!session.realm.isInTransaction) {
				throw IllegalStateException("realm should be in transaction at this point")
			}

			if (session.startDate == null) {
				throw ModelException("Start date should never be null here")
			}
			if (session.endDate == null) {
				throw ModelException("End date should never be null here")
			}
			val endDate = session.endDate!! // tested above
			val startDate = session.startDate!!

			val realm = session.realm

			val query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java)

			query
				.lessThanOrEqualTo("startDate", startDate)
				.greaterThanOrEqualTo("endDate", startDate)
				.or()
				.lessThanOrEqualTo("startDate", endDate)
				.greaterThanOrEqualTo("endDate", endDate)
				.or()
				.greaterThanOrEqualTo("startDate", startDate)
				.lessThanOrEqualTo("endDate", endDate)

			val sessionGroups = query.findAll()

			this.updateTimeFrames(sessionGroups, session)

		}

		/**
		 * Update the global timeline using the impacted [sessionSets] and the updated [session]
		 */
		private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, session: Session) {

			when (sessionSets.size) {
				0 -> this.createOrUpdateSessionSet(session)
				else -> this.mergeSessionGroups(session, sessionSets)
			}

		}

		/**
		 * Creates or update the session set for the [session]
		 */
		private fun createOrUpdateSessionSet(session: Session) {

			val set = session.sessionSet
			if (set != null) {
				set.startDate = session.startDate!! // tested above
				set.endDate = session.endDate!!
			} else {
				this.createSessionSet(session)
			}

		}

		/**
		 * Create a set and affect it to the [session]
		 */
		private fun createSessionSet(session: Session) {
			val set: SessionSet = SessionSet.newInstance(session.realm)
			set.startDate = session.startDate!!
			set.endDate = session.endDate!!
			set.breakDuration = session.breakDuration
			session.sessionSet = set
			set.computeStats()
		}

		/**
		 * Multiple session sets update:
		 * Merges all sets into one (delete all then create a new one)
		 */
		private fun mergeSessionGroups(session: Session, sessionSets: RealmResults<SessionSet>) {

			var startDate = session.startDate!!
			var endDate = session.endDate!!

			// get all endedSessions from sets
			val sessions = mutableSetOf<Session>()
			sessionSets.forEach { set ->
				set.sessions?.asIterable()?.let { sessions.addAll(it) }
			}

			// find earlier and later dates from all sets
			sessions.forEach { s ->

				if (s.startDate != null && s.endDate != null) {
					val start = s.startDate!!
					val end = s.endDate!!
					if (start.before(startDate)) {
						startDate = start
					}
					if (end.after(endDate)) {
						endDate = end
					}
				} else {
					throw CorruptSessionSetException("Set contains unfinished sessions!")
				}
			}

			// delete all sets
			sessionSets.deleteAllFromRealm()

			// Create a new set
			val set: SessionSet = SessionSet.newInstance(session.realm)
			set.startDate = startDate
			set.endDate = endDate

			// Add the session linked to this timeframe to the new sessionGroup
			session.sessionSet = set

			// Add all orphan endedSessions
			sessions.forEach { s ->
				s.sessionSet = set
				set.breakDuration = max(set.breakDuration, s.breakDuration)
			}
			set.computeStats()

//			Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}")

		}

		/**
		 * Removes the [session] from the timeline
		 */
		fun removeFromTimeline(session: Session) {

			if (!session.realm.isInTransaction) {
				throw IllegalStateException("realm should be in transaction at this point")
			}

			val sessionSet = session.sessionSet
			if (sessionSet != null) {

				val sessions = mutableSetOf<Session>()
				sessionSet.sessions?.asIterable()?.let { sessions.addAll(it) }
				sessions.remove(session)

				sessionSet.deleteFromRealm()

				sessions.forEach {
					updateTimeline(it)
				}
			}
		}

	}

}