package net.pokeranalytics.android.calculus.bankroll

import android.content.Context
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineDataSet
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.DatedValue
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.graph.DataSetFactory
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import java.util.*
import kotlin.collections.HashMap

//object BankrollReportManager {
//
//	var mainReport: BankrollReport? = null
//	var reports: MutableMap<String, BankrollReport> = mutableMapOf()
//
//	fun udpateBankrolls(bankrolls: List<Bankroll>) {
//		this.invalidateMainReport()
//		bankrolls.forEach {
//			this.reports.remove(it.id)
//		}
//	}
//
//	fun deleteBankrolls(bankrolls: List<Bankroll>) {
//		this.invalidateMainReport()
//		bankrolls.forEach {
//			this.reports.remove(it.id)
//		}
//	}
//
//	private fun invalidateMainReport() {
//		this.mainReport = null
//	}
//
//	private fun launchReports(bankrolls: List<Bankroll>) {
//
//		this.mainReport =  BankrollCalculator.computeReport()
//
//
//	}
//
//}

/**
 * This class holds the results from the BankrollCalculator computations
 * It has all the information required for the Bankroll various displays
 */
class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable {

	/**
	 * The value of the bankroll
	 */
	var total: Double = 0.0
		private set

	/**
	 * The initial value of the bankroll, or of all bankrolls if virtual is computed
	 */
	var initial: Double = 0.0
		set(value) {
			field = value
			this.computeBankrollTotal()
		}

	/**
	 * The net result from poker computables
	 */
	var netResult: Double = 0.0
		set(value) {
			field = value
			this.computeBankrollTotal()
		}

	/**
	 * The net result from transactions
	 */
	var transactionsNet: Double = 0.0
		set(value) {
			field = value
			this.computeBankrollTotal()
		}

	/**
	 * Computes the bankroll total
	 */
	private fun computeBankrollTotal() {
		this.total = this.initial + this.netResult + this.transactionsNet
	}

	/**
	 * The sum of all deposits
	 */
	var depositTotal: Double = 0.0
		set(value) {
			field = value
			this.computeNetBanked()
		}

	/**
	 * The sum of all withdrawals
	 */
	var withdrawalTotal: Double = 0.0
		set(value) {
			field = value
			this.computeNetBanked()
		}

	/**
	 * Computes the net banked amount
	 */
	private fun computeNetBanked() {
		this.netBanked = -this.withdrawalTotal - this.depositTotal
	}

	/**
	 * The difference between withdrawals and deposits
	 */
	var netBanked: Double = 0.0
		private set

	/**
	 * The risk of ruin
	 */
	var riskOfRuin: Double? = null

	/**
	 * The list of transactions held by the bankroll
	 */
	var transactions: List<Transaction> = mutableListOf()
		private set

	/**
	 * A map containing TransactionBuckets by transaction types
	 */
	var transactionBuckets: HashMap<String, TransactionBucket> = HashMap()
		private set

	/**
	 * The list of bankroll graph points
	 */
	private var evolutionPoints: MutableList<BRGraphPoint> = mutableListOf()

	/**
	 * The list of dated items used for the graph
	 */
	private var evolutionItems: MutableList<DatedValue> = mutableListOf()

	override val viewType: Int
		get() {
			return if (setup.bankroll == null) {
				RowViewType.LEGEND_DEFAULT.ordinal
			} else {
				RowViewType.TITLE_VALUE_ARROW.ordinal
			}
		}

	/**
	 * Adds a list of dated items to the evolution items used to get the bankroll graph
	 */
	fun addDatedItems(items: Collection<DatedValue>) {
		this.evolutionItems.addAll(items)
	}

	/**
	 * Adds a transaction to its type bucket
	 */
	fun addTransaction(transaction: Transaction) {

		transaction.type?.let { type ->

			var bucket = this.transactionBuckets[type.id]

			if (bucket == null) {
				val b = TransactionBucket(this.setup.virtualBankroll)
				this.transactionBuckets[type.id] = b
				bucket = b
			}

			bucket.addTransaction(transaction)

		} ?: run {
			throw IllegalStateException("Transaction has no type")
		}

	}

	/**
	 * Generates the graph points used for the virtual bankroll
	 */
	fun generateGraphPointsIfNecessary() {

		if (!this.setup.virtualBankroll) {
			return
		}

		this.evolutionItems.sortBy { it.date }

		var total = 0.0
		this.evolutionItems.forEach {
			total += it.amount
			val point = BRGraphPoint(total, it.date, it)
			this.evolutionPoints.add(point)
		}

	}

	/**
	 * Returns a data set used for the bankroll graph
	 */
	fun lineDataSet(context: Context): LineDataSet {

		val entries = mutableListOf<Entry>()
		this.evolutionPoints.forEachIndexed { index, point ->
			val entry = Entry(index.toFloat(), point.value.toFloat(), point.data)
			entries.add(entry)
		}
		return DataSetFactory.lineDataSetInstance(entries, "", context)
	}

}

/**
 * A class describing the parameters required to launch a bankroll report
 *
 */
class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null, val to: Date? = null) {

	/**
	 * Returns whether the setup concerns the virtual bankroll,
	 * i.e. the bankroll summing all concrete bankrolls
	 */
	val virtualBankroll: Boolean
		get() {
			return this.bankroll == null
		}

	/**
	 * the query used to get bankroll transactions
	 */
	val query: Query
		get() {
			val query = Query()

			this.bankroll?.let {
				val bankrollCondition = QueryCondition.AnyBankroll(bankroll)
				query.add(bankrollCondition)
			}
			this.from?.let {
				val fromCondition = QueryCondition.StartedFromDate()
				fromCondition.singleValue = it
				query.add(fromCondition)
			}
			this.to?.let {
				val toCondition = QueryCondition.StartedToDate()
				toCondition.singleValue = it
				query.add(toCondition)
			}
			return query
		}

	/**
	 * Returns whether or not the initial value should be added for the bankroll total
	 */
	val shouldAddInitialValue: Boolean
		get() {
			return this.from == null
		}

}

/**
 * A TransactionBucket holds a list of _transactions and computes its amount sum
 */
class TransactionBucket(useRate: Boolean = false) {

	/**
	 * Whether the bankroll rate should be used
	 */
	private var useRate: Boolean = useRate

	/**
	 * A list of _transactions
	 */
	private var _transactions: MutableList<Transaction> = mutableListOf()

	val transactions: List<Transaction>
		get() {
			return this._transactions
		}

	/**
	 * The sum of all _transactions
	 */
	var total: Double = 0.0
		private set


	fun addTransaction(transaction: Transaction) {

		this._transactions.add(transaction)
		var rate = 1.0
		if (this.useRate) {
			rate = transaction.bankroll?.currency?.rate ?: 1.0
		}

		val ratedAmount = rate * transaction.amount
		this.total += ratedAmount

	}

}

data class BRGraphPoint(var value: Double, var date: Date, var data: Any? = null) {

	var variation: Double = 0.0

}
