package net.pokeranalytics.android.model.realm

import android.text.InputType
import io.realm.*
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.SessionInterface
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.extensions.SessionState
import net.pokeranalytics.android.model.extensions.getState
import net.pokeranalytics.android.ui.adapter.components.LiveDataDataSource
import net.pokeranalytics.android.ui.adapter.components.RowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData
import net.pokeranalytics.android.ui.view.RowEditable
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.SessionRow
import net.pokeranalytics.android.util.data.sessionDao
import net.pokeranalytics.android.util.short
import net.pokeranalytics.android.util.toCurrency
import java.util.*
import kotlin.collections.ArrayList

open class Session : RealmObject(), SessionInterface, RowRepresentableDataSource, LiveDataDataSource,
	RowEditable {

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

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

	// The time frame of the Session, i.e. the start & end date
	var timeFrame: TimeFrame? = null

	// The time frame sessionGroup, which can contain multiple sessions
	override var sessionSet: SessionSet? = null

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

	// The bankroll hosting the results
	var bankroll: Bankroll? = null

	// 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

	// The big blind value
	var cgBigBlind: Double? = null

	// 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 tournamentType: TournamentName? = null

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

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

	/**
	 * Start a session
	 */
	fun startSession() {
		realm.executeTransaction {
			when (getState()) {
				SessionState.PENDING -> {
					val sessionTimeFrame = this.timeFrame ?: realm.createObject(TimeFrame::class.java)
					sessionTimeFrame.setDate(Date(), null)
					this.timeFrame = sessionTimeFrame
				}
				SessionState.PAUSED -> {
					this.timeFrame?.paused = false
				}
				else -> {
				}
			}
		}
	}

	/**
	 * Pause a session
	 */
	fun pauseSession() {
		realm.executeTransaction {
			when (getState()) {
				SessionState.STARTED -> {
					this.timeFrame?.paused = true
				}
			}
		}
	}

	/**
	 * Stop a session
	 */
	fun stopSession() {
		realm.executeTransaction {
			when (getState()) {
				SessionState.STARTED, SessionState.PAUSED -> {
					this.timeFrame?.paused = false
					this.timeFrame?.setDate(null, Date())
				}
			}
		}
	}

	/**
	 * 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 { set ->

			// get all sessions part of the deleted session set
			val sessionsFromSet = set.sessions

			// cleanup unecessary related objects
			set.deleteFromRealm()
			this.timeFrame?.deleteFromRealm()
			this.result?.deleteFromRealm()

			// make sessions recreate/find their session set
			sessionsFromSet?.let { sessions ->
				sessions.forEach { session ->
					session.timeFrame?.notifySessionDateChange()
				}
			}
		}

	}

	/**
	 * Return the game title
	 */
	fun getGameTitle(): String {
		var gameTitle = ""
		if (limit != null) {
			gameTitle += limit
		}
		if (game != null) {
			gameTitle += game?.title
		}
		return gameTitle
	}

	companion object {
		fun newInstance(): Session {
			var session: Session = Session()
			session.result = Result()
			session.timeFrame = TimeFrame()
			return session
		}
	}

	@Ignore // SessionInterface value
	override var value: Double = 0.0
		get() {
			return this.result?.net ?: 0.0
		}

	@Ignore
	override var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration?.toDouble() ?: 0.0)

	@Ignore
	override var bbNetResult: Double = 0.0
		get() {
			this.cgBigBlind?.let { bb ->
				this.result?.let { result ->
					return result.net / bb
				}
			}
			return 0.0
		}

	@Ignore
	override var bigBlindSessionCount: Int = if (this.cgBigBlind != null) 1 else 0

	@Ignore
	override var buyin: Double = 0.0
		get() {
			this.result?.let {
				it.buyin?.let {
					return it
				}
			}
			return 0.0
		}

	override fun adapterRows(): ArrayList<RowRepresentable> {
		val rows = ArrayList<RowRepresentable>()
		rows.addAll(SessionRow.getRowsForState(getState()))
		return rows
	}

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

	override fun stringForRow(row: RowRepresentable): String {
		return when (row) {
			SessionRow.BUY_IN -> buyin.toCurrency()
			SessionRow.BLINDS -> if (cgSmallBlind != null && cgBigBlind != null) "$cgSmallBlind / $cgBigBlind" else "--"
			SessionRow.GAME -> game?.title ?: "--"
			SessionRow.LOCATION -> location?.title ?: "--"
			SessionRow.BANKROLL -> bankroll?.title ?: "--"
			SessionRow.TABLE_SIZE -> tableSize?.toString() ?: "--"
			SessionRow.START_DATE -> if (timeFrame != null) timeFrame?.startDate?.short() ?: "--" else "--"
			SessionRow.END_DATE -> if (timeFrame != null) timeFrame?.endDate?.short() ?: "--" else "--"
			SessionRow.COMMENT -> if (comment.isNotEmpty()) comment else "--"
			else -> "--"
		}
	}

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


	override var title: String = "Change that: $creationDate"
	override val primaryKey: String get() = this.id

	override fun getBottomSheetData(row: RowRepresentable): ArrayList<BottomSheetData> {

		val data = ArrayList<BottomSheetData>()

		when (row) {

			SessionRow.BUY_IN -> {
				// Add first & second buttons values, current value & set the 2 edit texts
				data.add(BottomSheetData(100.0 * (cgBigBlind ?: 0.0)))
				data.add(BottomSheetData(200.0 * (cgBigBlind ?: 0.0)))
				data.add(BottomSheetData(buyin))
				data.add(BottomSheetData("",inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL))
				data.add(BottomSheetData("",inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL))
			}
			SessionRow.TIPS -> {
				// Disable the buttons with value = 0, add current value & set the 2 edit texts
				// TODO: manage tips
				data.add(BottomSheetData(cgSmallBlind ?: 0.0))
				data.add(BottomSheetData(cgBigBlind ?: 0.0))
				data.add(BottomSheetData(0))
				data.add(BottomSheetData("", inputType = InputType.TYPE_CLASS_NUMBER))
				data.add(BottomSheetData("", inputType = InputType.TYPE_CLASS_NUMBER))
			}
			SessionRow.TABLE_SIZE -> {data.add(BottomSheetData(tableSize))}
			SessionRow.GAME -> {
				// Add current game & games list
				data.add(BottomSheetData(limit))
				data.add(BottomSheetData(game, data = LiveData.GAME.items(realm)))
			}
			SessionRow.LOCATION -> {
				// Add current location and locations list
				data.add(BottomSheetData(location, data = LiveData.LOCATION.items(realm)))
			}
			SessionRow.BANKROLL -> {
				// Add current bankroll and bankrolls list
				data.add(BottomSheetData(bankroll, data = LiveData.BANKROLL.items(realm)))
			}
			SessionRow.BLINDS -> {
				data.add(BottomSheetData(cgSmallBlind, R.string.smallblind, InputType.TYPE_CLASS_NUMBER))
				data.add(BottomSheetData(cgBigBlind, R.string.bigblind, InputType.TYPE_CLASS_NUMBER))
			}
			SessionRow.COMMENT -> {
				data.add(BottomSheetData(comment, R.string.comment, InputType.TYPE_CLASS_TEXT))
			}
		}

		return data
	}

	override fun updateValue(value: Any?, row: RowRepresentable) {
		realm.beginTransaction()
		when (row) {

			SessionRow.BUY_IN -> {
				val localResult = if (result != null) result as Result else realm.createObject(Result::class.java)
				localResult.buyin = value as Double
				result = localResult
			}
			SessionRow.TABLE_SIZE -> tableSize = value as Int?
			SessionRow.GAME -> game = value as Game?
			SessionRow.BANKROLL -> bankroll = value as Bankroll?
			SessionRow.LOCATION -> location = value as Location?
			SessionRow.COMMENT -> comment = value as String? ?: ""
			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
				}
			}
			//TODO: Update
			SessionRow.START_DATE -> if (value is Date?) {
				if (value == null) {
					timeFrame = null
				} else {
					val timeFrameToUpdate = if (timeFrame != null) timeFrame as TimeFrame else realm.createObject(TimeFrame::class.java)
					timeFrameToUpdate.setDate(value, null)
					timeFrame = timeFrameToUpdate
				}
			}
			//TODO: Update
			SessionRow.END_DATE -> if (value is Date?) {
				val timeFrameToUpdate = if (timeFrame != null) timeFrame as TimeFrame else realm.createObject(TimeFrame::class.java)
				timeFrameToUpdate.setDate(null, value)
				timeFrame = timeFrameToUpdate
			}
		}
		realm.commitTransaction()
	}

}

enum class TournamentKind {
	MTT,
	SNG
}


/**
 * Session Dao
 */
class SessionDao(realmDb: Realm) {

	var realm: Realm = realmDb

	/**
	 * Create or update session
	 */
	fun createOrUpdateSession(session: Session): Session {
		realm.beginTransaction()
		val sessionToSave = realm.copyToRealmOrUpdate(session)
		realm.commitTransaction()
		return realm.copyFromRealm(sessionToSave)
	}

	/**
	 * Create or update sessions
	 */
	fun createOrUpdateSessions(sessions: List<Session>): List<Session> {

		realm.beginTransaction()

		// Update
		val sessionsToSave = realm.copyToRealmOrUpdate(sessions)
		realm.commitTransaction()

		return realm.copyFromRealm(sessionsToSave)
	}

	/**
	 * Find all sessions
	 */
	fun findAllSessions(): RealmResults<Session> {
		return realm.where(Session::class.java).findAll().sort("creationDate", Sort.DESCENDING)
	}

	/**
	 * Find session by id
	 */
	fun findSessionById(sessionId: Int): Session? {
		return realm.copyFromRealm(realm.where(Session::class.java).equalTo("id", sessionId).findFirst())
	}

	/**
	 * Delete session
	 */
	fun deleteSession(sessionId: Int) {
		realm.beginTransaction()
		realm.sessionDao().findSessionById(sessionId)?.deleteFromRealm()
		realm.commitTransaction()
	}

	/**
	 * Delete all sessions
	 */
	fun deleteAllSessions() {
		realm.beginTransaction()
		realm.sessionDao().findAllSessions().deleteAllFromRealm()
		realm.commitTransaction()
	}


}