package net.pokeranalytics.android.calculus import android.content.Context import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.FormattingException import net.pokeranalytics.android.model.interfaces.Timed import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.extensions.formatted import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import net.pokeranalytics.android.util.extensions.toCurrency import java.util.* class StatFormattingException(message: String) : Exception(message) { } class ObjectIdentifier(var id: String, var clazz: Class) { } interface StatEntry { val entryTitle: String fun formattedValue(stat: Stat, context: Context): TextFormat } enum class GraphType { LINE, BAR, } enum class AggregationType { SESSION, MONTH, YEAR, DURATION; val resId: Int get() { return when (this) { SESSION -> R.string.session MONTH -> R.string.month YEAR -> R.string.year DURATION -> R.string.duration } } } /** * An enum representing all the types of Session statistics */ enum class Stat : RowRepresentable { NETRESULT, HOURLY_RATE, AVERAGE, NUMBER_OF_SETS, NUMBER_OF_GAMES, DURATION, AVERAGE_DURATION, NET_BB_PER_100_HANDS, HOURLY_RATE_BB, AVERAGE_NET_BB, WIN_RATIO, AVERAGE_BUYIN, ROI, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS, HANDS_PLAYED, LOCATIONS_PLAYED, LONGEST_STREAKS, MAXIMUM_NETRESULT, MINIMUM_NETRESULT, MAXIMUM_DURATION, DAYS_PLAYED ; /** * Returns whether the stat evolution numericValues requires a distribution sorting */ fun hasDistributionSorting(): Boolean { return when (this) { STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true else -> false } } companion object { fun returnOnInvestment(netResult: Double, buyin: Double): Double? { if (buyin == 0.0) { return null } return netResult / buyin } fun netBBPer100Hands(netBB: Double, numberOfHands: Double): Double? { if (numberOfHands == 0.0) { return null } return netBB / numberOfHands * 100 } } override val resId: Int? get() { return when (this) { NETRESULT -> R.string.net_result HOURLY_RATE -> R.string.average_hour_rate AVERAGE -> R.string.average NUMBER_OF_SETS -> R.string.number_of_sessions NUMBER_OF_GAMES -> R.string.number_of_records DURATION -> R.string.duration AVERAGE_DURATION -> R.string.average_hours_played NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands HOURLY_RATE_BB -> R.string.average_hour_rate_bb_ AVERAGE_NET_BB -> R.string.average_net_result_bb_ WIN_RATIO -> R.string.win_ratio AVERAGE_BUYIN -> R.string.average_buyin ROI -> R.string.tournament_roi STANDARD_DEVIATION -> R.string.standard_deviation STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands HANDS_PLAYED -> R.string.number_of_hands LOCATIONS_PLAYED -> R.string.locations_played LONGEST_STREAKS -> R.string.longest_streaks MAXIMUM_NETRESULT -> R.string.max_net_result MINIMUM_NETRESULT -> R.string.min_net_result MAXIMUM_DURATION -> R.string.longest_session DAYS_PLAYED -> R.string.days_played } } /** * Formats the value of the stat to be suitable for display */ fun format(value: Double, secondValue: Double? = null, currency: Currency? = null, context: Context): TextFormat { if (value.isNaN()) { return TextFormat(NULL_TEXT, R.color.white) } when (this) { // Amounts + red/green NETRESULT, HOURLY_RATE, AVERAGE, MAXIMUM_NETRESULT, MINIMUM_NETRESULT -> { val color = if (value >= this.threshold) R.color.green else R.color.red return TextFormat(value.toCurrency(currency), color) } // Red/green numericValues HOURLY_RATE_BB, AVERAGE_NET_BB, NET_BB_PER_100_HANDS -> { val color = if (value >= this.threshold) R.color.green else R.color.red return TextFormat(value.formatted(), color) } // white integers NUMBER_OF_SETS, NUMBER_OF_GAMES, HANDS_PLAYED, LOCATIONS_PLAYED, DAYS_PLAYED -> { return TextFormat("${value.toInt()}") } // white durations DURATION, AVERAGE_DURATION, MAXIMUM_DURATION -> { return TextFormat(value.formattedHourlyDuration()) } // red/green percentages WIN_RATIO, ROI -> { val color = if (value * 100 >= this.threshold) R.color.green else R.color.red return TextFormat("${(value * 100).formatted()}%", color) } // white amountsr AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> { return TextFormat(value.toCurrency(currency)) } LONGEST_STREAKS -> { return TextFormat("${value.toInt()}W / ${secondValue!!.toInt()}L") } else -> throw FormattingException("Stat formatting of ${this.name} not handled") } } val threshold: Double get() { return when (this) { WIN_RATIO -> 50.0 else -> 0.0 } } fun cumulativeLabelResId(context: Context) : String { val resId = when (this) { AVERAGE, AVERAGE_DURATION, NET_BB_PER_100_HANDS, HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, WIN_RATIO, HOURLY_RATE -> R.string.average NUMBER_OF_SETS -> R.string.number_of_sessions NUMBER_OF_GAMES -> R.string.number_of_records NETRESULT, DURATION -> R.string.total STANDARD_DEVIATION -> R.string.net_result STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands else -> null } resId?.let { return context.getString(it) } ?: run { return NULL_TEXT } } val graphType: GraphType get() { return when (this) { NUMBER_OF_SETS, NUMBER_OF_GAMES -> GraphType.BAR else -> GraphType.LINE } } val aggregationTypes: List get() { return when (this) { NETRESULT -> listOf(AggregationType.SESSION, AggregationType.MONTH, AggregationType.YEAR, AggregationType.DURATION) NUMBER_OF_GAMES, NUMBER_OF_SETS -> listOf(AggregationType.MONTH, AggregationType.YEAR) else -> listOf(AggregationType.SESSION, AggregationType.MONTH, AggregationType.YEAR) } } val hasEvolutionGraph: Boolean get() { return when (this) { DURATION, AVERAGE_DURATION -> false else -> true } } val significantIndividualValue: Boolean get() { return when (this) { WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES -> false else -> true } } val shouldShowNumberOfSessions: Boolean get() { return when (this) { NUMBER_OF_GAMES, NUMBER_OF_SETS -> false else -> true } } override val viewType: Int = RowViewType.TITLE_VALUE.ordinal } /** * ComputedStat contains a [stat] and their associated [value] */ class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? = null, var currency: Currency? = null) { constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { if (previousValue != null) { this.variation = (value - previousValue) / previousValue } } /** * The variation of the stat */ var variation: Double? = null /** * Formats the value of the stat to be suitable for display */ fun format(context: Context): TextFormat { return this.stat.format(this.value, this.secondValue, this.currency, context) } }