package net.pokeranalytics.android.util.csv

import io.realm.Realm
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.utils.SessionUtils
import net.pokeranalytics.android.util.extensions.getOrCreate
import net.pokeranalytics.android.util.extensions.setHourMinutes
import org.apache.commons.csv.CSVRecord
import java.util.*

/**
 * The enumeration of Session fields
 */
sealed class SessionField {

	data class Start(
		override var header: String,
		override var callback: ((String) -> Date?)? = null,
		override val dateFormat: String? = null
	) : DateCSVField

	data class StartTime(
		override var header: String,
		override var callback: ((String) -> Date?)? = null,
		override val dateFormat: String? = null
	) : DateCSVField

	data class End(
		override var header: String,
		override var callback: ((String) -> Date?)? = null,
		override val dateFormat: String? = null
	) : DateCSVField

	data class EndTime(
		override var header: String,
		override var callback: ((String) -> Date?)? = null,
		override val dateFormat: String? = null
	) : DateCSVField

	data class Buyin(
		override var header: String,
		override var callback: ((String) -> Double?)? = null,
		override val numberFormat: String? = null
	) : NumberCSVField

	data class NetResult(
		override var header: String,
		override var callback: ((String) -> Double?)? = null,
		override val numberFormat: String? = null
	) : NumberCSVField

	data class CashedOut(
		override var header: String,
		override var callback: ((String) -> Double?)? = null,
		override val numberFormat: String? = null
	) : NumberCSVField

	data class Break(
		override var header: String,
		override var callback: ((String) -> Double?)? = null,
		override val numberFormat: String? = null
	) : NumberCSVField

	data class Tips(
		override var header: String,
		override var callback: ((String) -> Double?)? = null,
		override val numberFormat: String? = null
	) : NumberCSVField

	data class SmallBlind(
		override var header: String,
		override var callback: ((String) -> Double?)? = null,
		override val numberFormat: String? = null
	) : NumberCSVField

	data class BigBlind(
		override var header: String,
		override var callback: ((String) -> Double?)? = null,
		override val numberFormat: String? = null
	) : NumberCSVField

	data class Blind(override var header: String, override var callback: ((String) -> Pair<Double, Double>?)? = null) : BlindCSVField
	data class Game(override var header: String) : CSVField
	data class Location(override var header: String) : CSVField
	data class LocationType(override var header: String) : CSVField
	data class Bankroll(override var header: String) : CSVField
	data class LimitType(override var header: String) : CSVField
	data class Comment(override var header: String) : CSVField
	data class SessionType(override var header: String) : CSVField
	data class TableSize(override var header: String) : CSVField
	data class CurrencyCode(override var header: String) : CSVField
	data class TournamentName(override var header: String) : CSVField
	data class TournamentType(override var header: String) : CSVField

	data class CurrencyRate(
		override var header: String,
		override var callback: ((String) -> Double?)? = null,
		override val numberFormat: String? = null
	) : NumberCSVField

	data class TournamentPosition(
		override var header: String,
		override var callback: ((String) -> Double?)? = null,
		override val numberFormat: String? = null
	) : NumberCSVField

	data class TournamentNumberOfPlayers(
		override var header: String,
		override var callback: ((String) -> Double?)? = null,
		override val numberFormat: String? = null
	) : NumberCSVField
}

/**
 * A SessionCSVDescriptor is a CSVDescriptor specialized in parsing Session objects
 */
class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean, vararg elements: CSVField) : DataCSVDescriptor<Session>(source, *elements) {

	companion object {
		val pokerIncomeCash: CSVDescriptor = SessionCSVDescriptor(
			DataSource.POKER_INCOME,
			false,
			SessionField.Start("Start Time"),
			SessionField.End("End Time"),
			SessionField.Buyin("Buy In"),
			SessionField.CashedOut("Cashed Out"),
			SessionField.Break("Break Minutes"),
			SessionField.LimitType("Limit Type"),
			SessionField.Game("Game"),
			SessionField.Bankroll("Bankroll"),
			SessionField.Location("Location"),
			SessionField.Location("Location Type"),
			SessionField.Comment("Note"),
			SessionField.Tips("Tips"),
			SessionField.Blind("Stake")
		)

		val pokerBankrollTracker: CSVDescriptor = SessionCSVDescriptor(
			DataSource.POKER_BANKROLL_TRACKER,
			true,
			SessionField.Start("starttime", dateFormat = "MM/dd/yyyy HH:mm"),
			SessionField.End("endtime", dateFormat = "MM/dd/yyyy HH:mm"),
			SessionField.SessionType("variant"),
			SessionField.Buyin("buyin"),
			SessionField.CashedOut("cashout"),
			SessionField.Break("breakminutes"),
			SessionField.LimitType("limit"),
			SessionField.Game("game"),
			SessionField.Bankroll("currency"), // same as currency code
			SessionField.Location("location"),
			SessionField.Comment("sessionnote"),
			SessionField.Tips("expensesfromstack"),
			SessionField.SmallBlind("smallblind"),
			SessionField.BigBlind("bigblind"),
			SessionField.TournamentNumberOfPlayers("player"),
			SessionField.TournamentPosition("place"),
			SessionField.TournamentName("mttname"),
			SessionField.CurrencyCode("currency"),
			SessionField.CurrencyRate("exchangerate"),
			SessionField.TableSize("tablesize")
		)

		val runGoodTournaments: CSVDescriptor = SessionCSVDescriptor(
			DataSource.RUNGOOD,
			true,
			SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"),
			SessionField.StartTime("Start Time"),
			SessionField.End("End Date"),
			SessionField.EndTime("End Time"),
			SessionField.Buyin("Total Buy-In"),
			SessionField.CashedOut("Winnings"),
			SessionField.NetResult("Profit"),
			SessionField.Break("Break"),
			SessionField.LimitType("Limit Type"),
			SessionField.Game("Game"),
			SessionField.Bankroll("Bankroll"),
			SessionField.TableSize("Table Type"),
			SessionField.Location("Location"),
			SessionField.LocationType("Location Type"),
			SessionField.Comment("Notes"),
			SessionField.CurrencyCode("Currency"),
			SessionField.TournamentName("Event Name"),
			SessionField.TournamentNumberOfPlayers("Total Players"),
			SessionField.TournamentPosition("Finished Place"),
			SessionField.TournamentType("Single-Table/Multi-Table")

		)

		val runGoodCashGames: CSVDescriptor = SessionCSVDescriptor(
			DataSource.RUNGOOD,
			false,
			SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"),
			SessionField.StartTime("Start Time", dateFormat = "HH:mm"),
			SessionField.End("End Date", dateFormat = "dd/MM/yyyy"),
			SessionField.EndTime("End Time", dateFormat = "HH:mm"),
			SessionField.Buyin("Total Buy-In"),
			SessionField.CashedOut("Cashed Out"),
			SessionField.NetResult("Profit"),
			SessionField.Break("Break"),
			SessionField.LimitType("Limit Type"),
			SessionField.Game("Game"),
			SessionField.Bankroll("Bankroll"),
			SessionField.TableSize("Table Type"),
			SessionField.Location("Location"),
			SessionField.LocationType("Location Type"),
			SessionField.Comment("Notes"),
			SessionField.CurrencyCode("Currency"),
			SessionField.Blind("Stakes", callback = { value -> // $10/20
				value.drop(1)
				val blinds = value.split("/")
				if (blinds.size == 2) {
					return@Blind Pair(blinds.first().toDouble(), blinds.last().toDouble())
				} else {
					return@Blind null
				}
			})
		)

	}

	/**
	 * Parses a [record] and return an optional Session
	 */
	override fun parseData(realm: Realm, record: CSVRecord): Session? {

		val session = Session.newInstance(realm, this.isTournament)

		var isLive = true
		var bankrollName: String? = null
		var currencyCode: String? = null
		var currencyRate: Double? = null

		fields.forEach { field ->

			this.fieldMapping[field]?.let { index ->

				val value = record.get(index)
				when (field) {
					is SessionField.Start -> {
						session.startDate = field.parse(value)
					}
					is SessionField.End -> {
						session.endDate = field.parse(value)
					}
					is SessionField.StartTime -> { session.startDate?.setHourMinutes(value) }
					is SessionField.EndTime -> { session.endDate?.setHourMinutes(value) }
					is SessionField.Buyin -> session.result?.buyin = field.parse(value)
					is SessionField.CashedOut -> session.result?.cashout = field.parse(value)
					is SessionField.Tips -> session.result?.tips = field.parse(value)
					is SessionField.Break -> {
						field.parse(value)?.let {
							session.breakDuration = it.toLong() * 60 * 1000
						}
					}
					is SessionField.Game -> session.game = realm.getOrCreate(value)
					is SessionField.Location -> session.location = realm.getOrCreate(value)
					is SessionField.Bankroll -> bankrollName = value
					is SessionField.LimitType -> session.limit = Limit.getInstance(value)?.ordinal
					is SessionField.Comment -> session.comment = value
					is SessionField.Blind -> { // 1/2
						val blinds = field.parse(value)
						session.cgSmallBlind = blinds?.first
						session.cgBigBlind = blinds?.second
					}
					is SessionField.SmallBlind -> session.cgSmallBlind = field.parse(value)
					is SessionField.BigBlind -> session.cgBigBlind = field.parse(value)
					is SessionField.TableSize -> session.tableSize = TableSize.valueForLabel(value)
					is SessionField.SessionType -> {
						Session.Type.getValueFromString(value)?.let { type ->
							session.type = type.ordinal
						}
					}
					is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition = field.parse(value)?.toInt()
					is SessionField.TournamentName -> session.tournamentName = realm.getOrCreate(value)
					is SessionField.TournamentType -> session.tournamentType = TournamentType.getValueForLabel(value)?.ordinal
					is SessionField.TournamentNumberOfPlayers -> session.tournamentNumberOfPlayers = field.parse(value)?.toInt()
					is SessionField.CurrencyCode -> currencyCode = value
					is SessionField.CurrencyRate -> currencyRate = field.parse(value)
					else -> { }
				}
			}

		}

		session.bankroll = Bankroll.getOrCreate(realm, bankrollName ?: "Import", isLive, currencyCode, currencyRate)

		val startDate = session.startDate
		val endDate = session.endDate
		val net = session.result?.net

		return if (startDate != null && endDate != null && net != null) { // valid session
			val unique = SessionUtils.unicityCheck(realm, startDate, endDate, net)
			if (unique) session else null
		} else { // invalid session
			null
		}

	}

}