package net.pokeranalytics.android.model.realm

import android.content.Context
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Ignore
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.extensions.SessionState
import net.pokeranalytics.android.model.extensions.getState
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.filter.QueryCondition.*
import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.extensions.*
import java.util.*
import java.util.Currency
import kotlin.collections.ArrayList

typealias BB = Double

open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed,
    TimeFilterable, Filterable {

	enum class Type {
		CASH_GAME,
		TOURNAMENT
	}

	companion object {
		fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session {
			val session = Session()
			session.result = Result()
			if (bankroll != null) {
				session.bankroll = bankroll
			} else {
				session.bankroll = realm.where<Bankroll>().findFirst()
			}
			session.type = if (isTournament) Session.Type.TOURNAMENT.ordinal else Session.Type.CASH_GAME.ordinal
			return realm.copyToRealm(session)
		}

		fun fieldNameForQueryType(queryCondition: QueryCondition): String? {
			return when (queryCondition) {
				LIVE, ONLINE -> "bankroll.live"
				CASH, TOURNAMENT -> "type"
				is BANKROLL -> "bankroll.id"
                is GAME -> "game.id"
                is TOURNAMENT_NAME -> "tournamentName.id"
                is ANY_TOURNAMENT_FEATURES, is ALL_TOURNAMENT_FEATURES -> "tournamentFeatures.id"
                is LOCATION -> "location.id"
                is LIMIT -> "limit"
                is TABLE_SIZE -> "tableSize"
                is TOURNAMENT_TYPE -> "tournamentType"
                is BLIND -> "blinds"
                is COMMENT -> "comment"
                is BETWEEN_NUMBER_OF_TABLE, is MORE_NUMBER_OF_TABLE, is LESS_NUMBER_OF_TABLE -> "numberOfTable"
                is MORE_THAN_NET_RESULT, is LESS_THAN_NET_RESULT -> "computableResults.ratedNet"
                is MORE_THAN_BUY_IN, is LESS_THAN_BUY_IN -> "result.buyin"
                is MORE_THAN_CASH_OUT, is LESS_THAN_CASH_OUT -> "result.cashout"
                is MORE_THAN_TIPS, is LESS_THAN_TIPS -> "result.tips"
                is MORE_THAN_NUMBER_OF_PLAYER, is LESS_THAN_NUMBER_OF_PLAYER, is BETWEEN_NUMBER_OF_PLAYER -> "tournamentNumberOfPlayers"
                is MORE_THAN_TOURNAMENT_FEE, is LESS_THAN_TOURNAMENT_FEE, is BETWEEN_TOURNAMENT_FEE -> "tournamentEntryFee"
                is STARTED_FROM_DATE, is STARTED_TO_DATE -> "startDate"
                is ENDED_FROM_DATE, is ENDED_TO_DATE -> "endDate"
                is DAY_OF_WEEK, is WEEK_END, is WEEK_DAY -> "dayOfWeek"
                is MONTH -> "month"
                is YEAR -> "year"
                TODAY, YESTERDAY, TODAY_AND_YESTERDAY, THIS_YEAR, THIS_MONTH, THIS_WEEK -> "startDate"
				else -> null
			}
		}
	}

	@PrimaryKey
	override var id = UUID.randomUUID().toString()

	/**
	 * Indicates the type of session, cash game or tournament
	 */
	@Index
	var type: Int = Type.CASH_GAME.ordinal

	// The result of the main user
	var result: Result? = null

	@LinkingObjects("session")
	private val computableResults: RealmResults<ComputableResult>? = null

	@Ignore
	val computableResult: ComputableResult? = this.computableResults?.firstOrNull()

	// Timed interface

	override var dayOfWeek : Int? = null
	override var month: Int? = null
	override var year: Int? = null
	override var dayOfMonth: Int? = null

	/**
	 * The start date of the session
	 */
	var startDate: Date? = null
		set(value) {
			field = value

			this.updateTimeParameter(field)
			this.computeNetDuration()
			// nullifies endate when setting the start date after the end date
			if (value != null && this.endDate != null && value.after(this.endDate)) {
				this.endDate = null
			}
			this.dateChanged()
			this.computeStats()
		}

	/**
	 * the end date of the session
	 */
	@Index
	var endDate: Date? = null
		set(value) {
			field = value
			this.computeNetDuration()
			this.dateChanged()
			this.computeStats()
		}

	/**
	 * The break duration of the session
	 */
	override var breakDuration: Long = 0L
		set(value) {
			field = value
			this.computeNetDuration()
			this.computeStats()
		}

	/**
	 * the net duration of the session, automatically calculated
	 */
	override var netDuration: Long = 0L

	/**
	 * The start date of the break
	 */
	override var pauseDate: Date? = null
		set(value) {
			field = value
			this.updateRowRepresentation()
		}

	// The session set containing the sessions, which can contain multiple endedSessions
	var sessionSet: SessionSet? = null

	// the date of creation of the app
	var creationDate: Date = Date()

	// The bankroll hosting the results
	var bankroll: Bankroll? = null
		set(value) {
			field = value
			this.updateRowRepresentation()
		}

	// The limit type: NL, PL...
	var limit: Int? = null

	// The game played during the Session
	var game: Game? = null

	// The number of players at the table
	var tableSize: Int? = null

	// the location where the session is played
	var location: Location? = null

	// The number of tables played at the same time
	var numberOfTables: Int = 1

	// The hands list associated with the Session
	var hands: RealmList<HandHistory> = RealmList()

	// The list of opponents who participated to the session
	var opponents: RealmList<Player> = RealmList()

	// A comment written by the user
	var comment: String = ""

	// Cash Game

	// The small blind value
	var cgSmallBlind: Double? = null
        set(value) {
            field = value
            formatBlinds()
        }

	// The big blind value
	var cgBigBlind: Double? = null
		set(value) {
			field = value
			this.computeStats()
            formatBlinds()
		}

    var blinds: String? = null
        private set

	// Tournament

	// The entry fee of the tournament
	var tournamentEntryFee: Double? = null

	// The total number of players who participated in the tournament
	var tournamentNumberOfPlayers: Int? = null

	// The name of the tournament
	var tournamentName: TournamentName? = null

	// The kind of the tournament, MTT or SnG
	var tournamentType: Int? = null

	// The features of the tournament, like Knockout, Shootout, Turbo...
	var tournamentFeatures: RealmList<TournamentFeature> = RealmList()

    fun bankrollHasBeenUpdated() {
        formatBlinds()
    }

	/**
	 * Manages impacts on SessionSets
	 * Should be called when the start / end date are changed
	 */
	private fun dateChanged() {
		if (this.endDate != null) {
			SessionSetManager.updateTimeline(this)
		} else if (this.sessionSet != null) {
			SessionSetManager.removeFromTimeline(this)
		}
		this.updateRowRepresentation()
	}

	/**
	 * Returns a non-null date for the session
	 */
	override fun endDate(): Date {
		return this.endDate ?: Date()
	}

	/**
	 * Returns the nullable date
	 */
	override fun startDate(): Date? {
		return this.startDate
	}

	/**
	 * Return if this session is a tournament
	 */
	fun isTournament(): Boolean {
		return this.type == Type.TOURNAMENT.ordinal
	}

	/**
	 * Return if this session is a cash game
	 */
	fun isCashGame(): Boolean {
		return this.type == Type.CASH_GAME.ordinal
	}

	// Stats

	@Ignore
	val ONLINE_PLAYER_HANDS_PER_HOUR = 400.0
	@Ignore
	val LIVE_PLAYER_HANDS_PER_HOUR = 250.0

	/**
	 * The net result in big blinds
	 */
	val bbNet: BB
		get() {
			val bb = this.cgBigBlind; val result = this.result
			if (bb != null && result != null) {
				return result.net / bb
			} else {
				return 0.0
			}
		}

	/**
	 * The estimation of the number of hands played
	 */
	@Ignore
	var estimatedHands: Double = 0.0
		get() {
			val noh = this.numberOfHandsPerHour
			val hd = this.hourlyDuration
			return noh * hd
		}

	/**
	 * Pre-compute various stats
	 */
	fun computeStats() {

		// ComputableResult are created only when a session is over
		if (this.startDate != null && this.endDate != null && this.computableResults?.size == 0) {
			val computableResult = realm.createObject(ComputableResult::class.java)
			computableResult.session = this
		}  // if a ComputableResult exists and the session is not completed, delete it
		else if ((this.startDate == null || this.endDate == null) && this.computableResult != null && this.computableResult.isValid) {
			this.computableResult.deleteFromRealm()
		}

		// Update the ComputableResult
		this.computableResults?.forEachIndexed { index, computableResult ->
			computableResult.updateWith(this)
			if (index > 0) {
				throw IllegalStateException("Session cannot have more than one computable result")
			}
		}
		this.sessionSet?.computeStats()
	}

	/**
	 * Approximates the number of hands played per hour at the table
	 */
	val numberOfHandsPerHour: Double
		get() {
			val tableSize = this.tableSize ?: 9 // 9 is the default table size if null
			val isLive = this.bankroll?.live ?: true
			val playerHandsPerHour = if (isLive) LIVE_PLAYER_HANDS_PER_HOUR else ONLINE_PLAYER_HANDS_PER_HOUR
			return playerHandsPerHour / tableSize.toDouble()
		}

	@Ignore
	var ratedBuyin: Double = 0.0
		get() {
			val rate = this.bankroll?.currency?.rate ?: 1.0
			this.result?.buyin?.let { buyin ->
				return buyin * rate
			}
			return 0.0
		}

	val hourlyRate: Double
		get() {
			this.result?.let { result ->
				return result.net / this.hourlyDuration
			}
			throw ModelException("Session should have an existing Result relationship")
		}

	val bbHourlyRate: BB
		get() {
			return this.bbNet / this.hourlyDuration
		}

	// Manageable

	override fun isValidForSave(): Boolean {
		return this.bankroll != null
	}

	override fun alreadyExists(realm: Realm): Boolean {
		return false
	}

	override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
		return R.string.no_br_popup_message
	}

	// States

	/**
	 * Start or continue a session
	 */
	fun startOrContinue() {
		realm.executeTransaction {
			when (getState()) {
				SessionState.PENDING, SessionState.PLANNED -> {
					this.startDate = Date()
					if (this.tournamentEntryFee != null) {
						this.result?.buyin = this.tournamentEntryFee
					}
				}
				SessionState.PAUSED -> {
					val pauseDate = this.pauseDate
					if (pauseDate != null) {
						this.breakDuration += Date().time - pauseDate.time
					} else {
						throw IllegalStateException("When resuming, the pause date must be set")
					}
					this.pauseDate = null
				}
				else -> {
					throw IllegalStateException("unmanaged session state")
				}
			}
		}
	}

	/**
	 * Pause a session
	 */
	fun pause() {
		realm.executeTransaction {
			when (getState()) {
				SessionState.STARTED -> {
					this.pauseDate = Date()
				}
				else -> throw IllegalStateException("Pausing a session in an unmanaged state")
			}
		}
	}

	/**
	 * Stop a session
	 */
	fun stop() {
		realm.executeTransaction {
			when (getState()) {
				SessionState.STARTED, SessionState.PAUSED -> {
					this.end()
				}
				else -> throw Exception("Stopping session in unmanaged state")
			}
		}
	}

	/**
	 * Restart a session
	 */
	fun restart() {
		realm.executeTransaction {
			this.pauseDate = null
			this.startDate = Date()
			this.endDate = null
			this.breakDuration = 0L
		}
	}

	/**
	 * Utility method to cleanly end a session
	 */
	fun end() {
		this.pauseDate = null
		if (this.endDate == null) {
			this.endDate = Date()
		}
	}

	// Formatters

	/**
	 * Return the netDuration of the current session
	 */
	fun getFormattedDuration(): String {
		this.startDate?.let { start ->
			val netDuration = this.endDate().time - start.time - this.breakDuration
			return netDuration.toMinutes()
		}
		return NULL_TEXT
	}

    val hasDefaultCurrency: Boolean
        get() {
            return bankroll?.currency?.code == null
        }

    val currency : Currency
        get() {
            return bankroll?.currency?.code?.let {
                Currency.getInstance(it)
            } ?: run {
                UserDefaults.currency
            }
        }

	/**
	 * Return the game title
	 * Example: NL Holdem
	 */
	fun getFormattedGame(): String {
		var gameTitle = ""
		limit?.let {
			if (it >= 0 && it < Limit.values().size) {
				gameTitle += Limit.values()[it].shortName + " "
			}
		}
		if (game != null) {
			gameTitle += game?.name
		}
		return if (gameTitle.isNotBlank()) gameTitle else NULL_TEXT
	}

    fun getFormattedBlinds(): String {
        return blinds ?: NULL_TEXT
    }

    private fun formatBlinds() {
        blinds = null
        if (cgBigBlind == null) return
        cgBigBlind?.let { bb ->
            val sb = cgSmallBlind ?: bb / 2.0
            blinds = "${currency.symbol} ${sb.formatted()}/${bb.round()}"
        }
    }

	// LifeCycle

	/**
	 * Delete the object from realm
	 * @TODO: Cascade delete?
	 */
	fun delete() {
		realm.executeTransaction {
			cleanup()
			deleteFromRealm()
		}
	}

	/**
	 * This method is called whenever a session is about to be deleted
	 */
	fun cleanup() {

		this.sessionSet?.let {

			// Updates the timeline
			SessionSetManager.removeFromTimeline(this)

			// cleanup unnecessary related objects
			this.result?.deleteFromRealm()
			this.computableResults?.deleteAllFromRealm()

		}
	}

	@Ignore
	override val viewType: Int = RowViewType.ROW_SESSION.ordinal

	override fun getDisplayName(): String {
		return "Session ${this.creationDate}"
	}

	@Ignore
	private var rowRepresentationForCurrentState : List<RowRepresentable> = mutableListOf()

	private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> {
		val rows = ArrayList<RowRepresentable>()

		// Headers
		when (getState()) {
			SessionState.STARTED -> {
				rows.add(
					CustomizableRowRepresentable(
						RowViewType.HEADER_TITLE_AMOUNT_BIG,
						title = getFormattedDuration(),
						computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, currency = currency)
					)
				)
				rows.add(SeparatorRow())
			}
			SessionState.PAUSED -> {
				rows.add(
					CustomizableRowRepresentable(
						RowViewType.HEADER_TITLE_AMOUNT_BIG,
						resId = R.string.pause,
						computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, currency = currency)
					)
				)
				rows.add(SeparatorRow())
			}
			SessionState.FINISHED -> {
				rows.add(
					CustomizableRowRepresentable(
						RowViewType.HEADER_TITLE_AMOUNT_BIG,
						title = getFormattedDuration(),
						computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, currency = currency)
					)
				)
				rows.add(
					CustomizableRowRepresentable(
						RowViewType.HEADER_TITLE_AMOUNT,
						resId = R.string.hour_rate_without_pauses,
						computedStat = ComputedStat(Stat.HOURLY_RATE, this.hourlyRate, currency = currency)
					)
				)

//				if (!isTournament()) {
//					rows.add(
//						CustomizableRowRepresentable(
//							RowViewType.HEADER_TITLE_VALUE,
//							resId = R.string.bankroll_variation,
//							computedStat = ComputedStat(Stat.HOURLY_RATE, 0.0, CurrencyUtils.getCurrency(bankroll))
//						)
//					)
//				}
				rows.add(SeparatorRow())
			}
			else -> {
			}
		}

		// Rows
		rows.addAll(SessionRow.getRows(this))
		return rows
	}

	fun updateRowRepresentation() {
		this.rowRepresentationForCurrentState = this.updatedRowRepresentationForCurrentState()
	}

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

	override fun boolForRow(row: RowRepresentable): Boolean {
		return false
	}

	override fun stringForRow(row: RowRepresentable, context: Context): String {
		return when (row) {
			SessionRow.BANKROLL -> bankroll?.name ?: NULL_TEXT
			SessionRow.BLINDS -> getFormattedBlinds()
			SessionRow.BREAK_TIME -> if (this.breakDuration > 0.0) this.breakDuration.toMinutes() else NULL_TEXT
			SessionRow.BUY_IN -> this.result?.buyin?.toCurrency(currency) ?: NULL_TEXT
			SessionRow.CASHED_OUT, SessionRow.PRIZE -> this.result?.cashout?.toCurrency(currency) ?: NULL_TEXT
			SessionRow.NET_RESULT -> this.result?.netResult?.toCurrency(currency) ?: NULL_TEXT
			SessionRow.COMMENT -> if (this.comment.isNotEmpty()) this.comment else NULL_TEXT
			SessionRow.END_DATE -> this.endDate?.shortDateTime() ?: NULL_TEXT
			SessionRow.GAME -> getFormattedGame()
			SessionRow.INITIAL_BUY_IN -> tournamentEntryFee?.toCurrency(currency) ?: NULL_TEXT
			SessionRow.LOCATION -> location?.name ?: NULL_TEXT
			SessionRow.PLAYERS -> tournamentNumberOfPlayers?.toString() ?: NULL_TEXT
			SessionRow.POSITION -> result?.tournamentFinalPosition?.toString() ?: NULL_TEXT
			SessionRow.START_DATE -> this.startDate?.shortDateTime() ?: NULL_TEXT
			SessionRow.TABLE_SIZE -> this.tableSize?.let { TableSize(it).localizedTitle(context) } ?: NULL_TEXT
			SessionRow.TIPS -> result?.tips?.toCurrency(currency) ?: NULL_TEXT
			SessionRow.TOURNAMENT_TYPE -> this.tournamentType?.let {
				TournamentType.values()[it].localizedTitle(context)
			} ?: run {
				NULL_TEXT
			}
			SessionRow.TOURNAMENT_FEATURE -> {
				if (tournamentFeatures.size > 2) {
					"${tournamentFeatures.subList(0,2).joinToString {
						it.name
					}}, ..."
				} else if (tournamentFeatures.size > 0) {
					tournamentFeatures.joinToString {
						it.name
					}
				} else {
					NULL_TEXT
				}
			}
			SessionRow.TOURNAMENT_NAME -> tournamentName?.name ?: NULL_TEXT
			else -> throw UnmanagedRowRepresentableException("Unmanaged row = ${row.getDisplayName()}")
		}
	}

	override fun actionIconForRow(row: RowRepresentable): Int? {
		return when (row) {
			SessionRow.START_DATE, SessionRow.END_DATE -> {
				R.drawable.ic_close
			}
			else -> null
		}
	}

	override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
		return when (row) {
			SessionRow.BANKROLL -> row.editingDescriptors(mapOf(
				"defaultValue" to this.bankroll,
				"data" to LiveData.BANKROLL.items(realm)))
			SessionRow.GAME -> row.editingDescriptors(mapOf(
				"limit" to this.limit,
				"defaultValue" to this.game,
				"data" to LiveData.GAME.items(realm)))
			SessionRow.LOCATION -> row.editingDescriptors(mapOf(
				"defaultValue" to this.location,
				"data" to LiveData.LOCATION.items(realm)))
			SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(mapOf(
				"defaultValue" to this.tournamentFeatures,
				"data" to LiveData.TOURNAMENT_FEATURE.items(realm)))
			SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(mapOf(
				"defaultValue" to this.tournamentName,
				"data" to LiveData.TOURNAMENT_NAME.items(realm)))
			SessionRow.TOURNAMENT_TYPE -> row.editingDescriptors(mapOf(
				"defaultValue" to this.tournamentType))
			SessionRow.TABLE_SIZE -> row.editingDescriptors(mapOf(
				"defaultValue" to this.tableSize))
			SessionRow.BLINDS -> row.editingDescriptors(mapOf(
				"sb" to cgSmallBlind?.round(),
				"bb" to cgBigBlind?.round()
			))
			SessionRow.BUY_IN -> row.editingDescriptors(mapOf(
				"bb" to cgBigBlind,
				"fee" to this.tournamentEntryFee,
				"ratedBuyin" to result?.buyin
			))
			SessionRow.BREAK_TIME -> row.editingDescriptors(mapOf())
			SessionRow.CASHED_OUT, SessionRow.PRIZE -> row.editingDescriptors(mapOf(
				"defaultValue" to result?.cashout
			))
			SessionRow.NET_RESULT -> row.editingDescriptors(mapOf(
				"defaultValue" to result?.netResult
			))
			SessionRow.COMMENT -> row.editingDescriptors(mapOf(
				"defaultValue" to this.comment))
			SessionRow.INITIAL_BUY_IN -> row.editingDescriptors(mapOf(
				"defaultValue" to this.tournamentEntryFee
			))
			SessionRow.PLAYERS -> row.editingDescriptors(mapOf(
				"defaultValue" to this.tournamentNumberOfPlayers))
			SessionRow.POSITION -> row.editingDescriptors(mapOf(
				"defaultValue" to this.result?.tournamentFinalPosition))
			SessionRow.TIPS -> row.editingDescriptors(mapOf(
				"sb" to cgSmallBlind?.round(),
				"bb" to cgBigBlind?.round(),
				"tips" to result?.tips
			))
			else -> null
		}
	}

	override fun updateValue(value: Any?, row: RowRepresentable) {

		realm.executeTransaction {

			when (row) {
				SessionRow.BANKROLL -> bankroll = value as Bankroll?
				SessionRow.BLINDS -> if (value is ArrayList<*>) {
					cgSmallBlind = try {
						(value[0] as String? ?: "0").toDouble()
					} catch (e: Exception) {
						null
					}

					cgBigBlind = try {
						(value[1] as String? ?: "0").toDouble()
					} catch (e: Exception) {
						null
					}

					cgBigBlind?.let {
						if (cgSmallBlind == null || cgSmallBlind == 0.0) {
							cgSmallBlind = it / 2.0
						}
					}
				} else if (value == null) {
					cgSmallBlind = null
					cgBigBlind = null
				}
				SessionRow.BREAK_TIME -> {
					this.breakDuration = (value as Double? ?: 0.0).toLong() * 60 * 1000
				}
				SessionRow.BUY_IN -> {
					val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
					localResult.buyin = value as Double?
					this.result = localResult
					this.updateRowRepresentation()
				}
				SessionRow.CASHED_OUT, SessionRow.PRIZE -> {
					val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java)

					localResult.cashout = value as Double?

					this.result = localResult
				}
				SessionRow.NET_RESULT -> {
					this.result?.let { result ->
						result.netResult = value as Double?
					}
				}
				SessionRow.COMMENT -> comment = value as String? ?: ""

				SessionRow.END_DATE -> if (value is Date?) {
					this.endDate = value
				}
				SessionRow.GAME -> {
					if (value is ArrayList<*>) {
						limit = try {
							(value[0] as Int?)
						} catch (e: Exception) {
							null
						}
						game = try {
							(value[1] as Game?)
						} catch (e: Exception) {
							null
						}
					} else if (value is Game) {
						game = value
					} else if (value == null) {
						limit = null
						game = null
					}
				}
				SessionRow.INITIAL_BUY_IN -> {
					this.tournamentEntryFee = (value as Double?)
				}
				SessionRow.LOCATION -> location = value as Location?
				SessionRow.PLAYERS -> {
					if (value is Double) {
						this.tournamentNumberOfPlayers = value.toInt()
					} else {
						this.tournamentNumberOfPlayers = null
					}
				}
				SessionRow.POSITION -> {
					val localResult = if (result != null) result as Result else realm.createObject(Result::class.java)
					if (value is Double) {
						localResult.tournamentFinalPosition = value.toInt()
					} else {
						localResult.tournamentFinalPosition = null
					}
					result = localResult
				}
				SessionRow.START_DATE -> if (value is Date) {
					this.startDate = value
				}
				SessionRow.TABLE_SIZE -> tableSize = value as Int?
				SessionRow.TIPS -> {
					val localResult = if (result != null) result as Result else realm.createObject(Result::class.java)
					localResult.tips = value as Double?
					result = localResult
				}
				SessionRow.TOURNAMENT_NAME -> tournamentName = value as TournamentName?
				SessionRow.TOURNAMENT_TYPE -> tournamentType = value as Int?
				SessionRow.TOURNAMENT_FEATURE -> {

					value?.let {
						tournamentFeatures = RealmList()
						tournamentFeatures.addAll((it as ArrayList<TournamentFeature>))
					} ?: run {
						tournamentFeatures.removeAll(this.tournamentFeatures)
					}
				}
			}
		}

	}

	// Stat Base

	override fun formattedValue(stat: Stat, context: Context) : TextFormat {

		this.result?.let { result ->

			val value: Double? = when (stat) {
				Stat.NETRESULT, Stat.AVERAGE, Stat.STANDARD_DEVIATION -> result.net
				Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> 1.0
				Stat.AVERAGE_BUYIN -> result.buyin
				Stat.ROI -> {
					result.buyin?.let {
						Stat.returnOnInvestment(result.net, it)
					} ?: run {
						null
					}
				}
				Stat.HOURLY_RATE_BB -> this.bbHourlyRate
				Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> Stat.netBBPer100Hands(this.bbNet, this.estimatedHands)
				Stat.AVERAGE_NET_BB -> this.bbNet
				Stat.DURATION, Stat.AVERAGE_DURATION -> this.netDuration.toDouble()
				Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate
				Stat.HANDS_PLAYED -> this.estimatedHands
				Stat.WIN_RATIO -> null
				else -> throw StatFormattingException("format undefined for stat ${stat.name}")
			}

			value?.let {
				return stat.format(it, currency = currency, context = context)
			} ?: run {
				return TextFormat(NULL_TEXT)
			}

		} ?: run {
			throw java.lang.IllegalStateException("Asking for stats on Session without Result")
		}

	}

	// Timed

	override val objectIdentifier: ObjectIdentifier
		get() = ObjectIdentifier(this.id, Session::class.java)


}

