package net.pokeranalytics.android.calculus import android.content.Context import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.FormattingException import net.pokeranalytics.android.exceptions.PAIllegalStateException 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.TextFormat import net.pokeranalytics.android.util.enumerations.IntIdentifiable import net.pokeranalytics.android.util.enumerations.IntSearchable import net.pokeranalytics.android.util.extensions.formatted import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import net.pokeranalytics.android.util.extensions.toCurrency import java.util.* import kotlin.math.exp import kotlin.math.pow class StatFormattingException(message: String) : Exception(message) /** * An enum representing all the types of Session statistics */ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepresentable { NET_RESULT(1), BB_NET_RESULT(2), HOURLY_RATE(3), AVERAGE(4), NUMBER_OF_SETS(5), NUMBER_OF_GAMES(6), HOURLY_DURATION(7), AVERAGE_HOURLY_DURATION(8), NET_BB_PER_100_HANDS(9), HOURLY_RATE_BB(10), AVERAGE_NET_BB(11), WIN_RATIO(12), AVERAGE_BUYIN(13), ROI(14), STANDARD_DEVIATION(15), STANDARD_DEVIATION_HOURLY(16), STANDARD_DEVIATION_BB_PER_100_HANDS(17), HANDS_PLAYED(18), LOCATIONS_PLAYED(19), LONGEST_STREAKS(20), MAXIMUM_NETRESULT(21), MINIMUM_NETRESULT(22), MAXIMUM_DURATION(23), DAYS_PLAYED(24), WINNING_SESSION_COUNT(25), BB_SESSION_COUNT(26), TOTAL_BUYIN(27), RISK_OF_RUIN(28), STANDARD_DEVIATION_BB(29), TOURNAMENT_ITM_RATIO(30), TOTAL_TIPS(31) ; companion object : IntSearchable { override fun valuesInternal(): Array { return values() } val userSelectableList: List get() { return values().filter { it.canBeUserSelected } } val evolutionValuesList: List get() { return values().filter { it.hasProgressValues } } 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 } fun riskOfRuin(hourlyRate: Double, hourlyStandardDeviation: Double, bankrollValue: Double): Double? { if (bankrollValue <= 0.0) { return null } val numerator = -2 * hourlyRate * bankrollValue val denominator = hourlyStandardDeviation.pow(2.0) val ratio = numerator / denominator return exp(ratio) } } override val resId: Int? get() { return when (this) { NET_RESULT -> R.string.net_result BB_NET_RESULT -> R.string.total_net_result_bb_ 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 HOURLY_DURATION -> R.string.duration AVERAGE_HOURLY_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 STANDARD_DEVIATION_BB -> R.string.bb_standard_deviation 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 TOTAL_BUYIN -> R.string.total_buyin TOURNAMENT_ITM_RATIO -> R.string.itm_ratio TOTAL_TIPS -> R.string.total_tips else -> throw PAIllegalStateException("Stat ${this.name} name required but undefined") } } /** * Formats the value of the stat to be suitable for display */ fun textFormat(value: Double, secondValue: Double? = null, currency: Currency? = null): TextFormat { if (value.isNaN()) { return TextFormat(NULL_TEXT, R.color.white) } when (this) { // Amounts + red/green NET_RESULT, 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, BB_NET_RESULT -> { 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 HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> { return TextFormat(value.formattedHourlyDuration()) } // red/green percentages WIN_RATIO, ROI, TOURNAMENT_ITM_RATIO -> { val color = if (value * 100 >= this.threshold) R.color.green else R.color.red return TextFormat("${(value * 100).formatted}%", color) } RISK_OF_RUIN -> { val color = if (value * 100 <= this.threshold) R.color.green else R.color.red return TextFormat("${(value * 100).formatted}%", color) } // white amounts AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS, STANDARD_DEVIATION_BB, TOTAL_BUYIN, TOTAL_TIPS -> { 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") } } private val threshold: Double get() { return when (this) { RISK_OF_RUIN -> 5.0 TOURNAMENT_ITM_RATIO -> 10.0 WIN_RATIO -> 50.0 else -> 0.0 } } /** * Returns a label used to display the legend right value, typically a total or an average */ fun cumulativeLabelResId(context: Context): String { val resId = when (this) { AVERAGE, AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS, HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, HOURLY_RATE -> R.string.average NUMBER_OF_SETS -> R.string.number_of_sessions NUMBER_OF_GAMES -> R.string.number_of_records NET_RESULT -> R.string.total STANDARD_DEVIATION -> R.string.net_result STANDARD_DEVIATION_BB -> R.string.average_net_result_bb_ STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands WIN_RATIO, HOURLY_DURATION, TOURNAMENT_ITM_RATIO -> return this.localizedTitle(context) else -> null } resId?.let { return context.getString(it) } ?: run { return NULL_TEXT } } /** * Returns the different available aggregation type for each statistic */ val aggregationTypes: List get() { return when (this) { NET_RESULT -> 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) } } /** * Returns if the stat has an evolution graph */ val hasProgressGraph: Boolean get() { return when (this) { HOURLY_DURATION, AVERAGE_HOURLY_DURATION, STANDARD_DEVIATION_BB, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, HANDS_PLAYED, STANDARD_DEVIATION_BB_PER_100_HANDS, TOTAL_TIPS -> false else -> true } } val isStandardDeviation: Boolean get() { return when (this) { STANDARD_DEVIATION, STANDARD_DEVIATION_BB, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true else -> false } } val legendHideRightValue: Boolean get() { return when (this) { AVERAGE, NUMBER_OF_SETS, NUMBER_OF_GAMES, WIN_RATIO, TOURNAMENT_ITM_RATIO, HOURLY_DURATION, AVERAGE_HOURLY_DURATION -> true else -> false } } /** * Returns if the stat has a significant value to display in a progress graph */ val graphSignificantIndividualValue: Boolean get() { return when (this) { AVERAGE, WIN_RATIO, TOURNAMENT_ITM_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, STANDARD_DEVIATION, HOURLY_DURATION -> false else -> true } } /** * Returns if the stat graph should show the number of sessions */ val graphShouldShowNumberOfSessions: Boolean get() { return when (this) { NUMBER_OF_GAMES, NUMBER_OF_SETS -> false else -> true } } val graphShowsXAxisZero: Boolean get() { return when (this) { HOURLY_DURATION -> true else -> false } } val graphShowsYAxisZero: Boolean get() { return when (this) { HOURLY_DURATION -> true else -> false } } private val canBeUserSelected: Boolean get() { return when (this) { WINNING_SESSION_COUNT, BB_SESSION_COUNT, RISK_OF_RUIN -> false else -> true } } private val hasProgressValues: Boolean get() { return when (this) { NET_RESULT, NET_BB_PER_100_HANDS, HOURLY_RATE_BB, AVERAGE_HOURLY_DURATION, HOURLY_DURATION, NUMBER_OF_SETS, ROI, AVERAGE_BUYIN, WIN_RATIO, TOURNAMENT_ITM_RATIO, AVERAGE_NET_BB, NUMBER_OF_GAMES, AVERAGE -> true else -> false } } override val viewType: Int = RowViewType.TITLE_VALUE.ordinal }