package net.pokeranalytics.android.model.comparison

import io.realm.Realm
import io.realm.RealmQuery
import io.realm.Sort
import io.realm.kotlin.where
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.*
import java.util.*
import kotlin.collections.ArrayList

enum class Comparator {
	DAY_OF_WEEK,
	MONTH_OF_YEAR,
	YEAR,
	BLIND,
	CASH,
	TOURNAMENT,
    LIVE,
    ONLINE,
    GAME,
    BANKROLL,
    LOCATION,
    TOURNAMENT_FEATURE,
    TOURNAMENT_NAME,
    DAY_PERIOD,
    LIMIT_TYPE,
    TABLE_SIZE,
    TOURNAMENT_TYPE,
	;

	val queryConditions: List<QueryCondition>
		get() {
			return when (this) {
				MONTH_OF_YEAR -> List(12) { index -> QueryCondition.MONTH().apply { setMonth(index) } }
				DAY_OF_WEEK -> List(7) { index -> QueryCondition.DAY_OF_WEEK().apply { setDay(index) } }
				YEAR -> {
					val years = arrayListOf<QueryCondition.YEAR>()
					val calendar = Calendar.getInstance()
					calendar.time = Date()
					val yearNow = calendar.get(Calendar.YEAR)
					val realm = Realm.getDefaultInstance()
					realm.where<Session>().sort("year", Sort.ASCENDING).findFirst()?.year?.let {
						for (index in 0..(yearNow - it)) {
							years.add(QueryCondition.YEAR().apply { setYear(yearNow - index) })
						}
					}
					realm.close()
					years
				}
                DAY_PERIOD -> listOf(QueryCondition.WEEK_DAY, QueryCondition.WEEK_END)
				CASH -> listOf(QueryCondition.CASH)
				TOURNAMENT -> listOf(QueryCondition.TOURNAMENT)
                LIVE -> listOf(QueryCondition.LIVE)
                ONLINE -> listOf(QueryCondition.ONLINE)
                BLIND -> {
                    val blinds = arrayListOf<QueryCondition.BLIND>()
                    val realm = Realm.getDefaultInstance()
                    realm.where<Session>().distinct("blind", "bankroll.currency.code").findAll().sort("cgSmallBlind", Sort.ASCENDING).map {
                        it.blinds?.let { stake ->
                            blinds.add(QueryCondition.BLIND().apply { setBlind(stake, it.hasDefaultCurrency)})
                        }
                    }
                    realm.close()
                    blinds
                }
                GAME -> {
                    val objects = arrayListOf<QueryCondition.GAME>()
                    val realm = Realm.getDefaultInstance()
                    realm.where<Game>().findAll().forEach {
                        objects.add(QueryCondition.GAME().apply { setObjectId(it.id)})
                    }
                    realm.close()
                    objects
                }
                BANKROLL -> {
                    val objects = arrayListOf<QueryCondition.BANKROLL>()
                    val realm = Realm.getDefaultInstance()
                    realm.where<Bankroll>().findAll().forEach {
                        objects.add(QueryCondition.BANKROLL().apply { setObjectId(it.id)})
                    }
                    realm.close()
                    objects
                }
                LOCATION -> {
                    val objects = arrayListOf<QueryCondition.LOCATION>()
                    val realm = Realm.getDefaultInstance()
                    realm.where<Location>().findAll().forEach {
                        objects.add(QueryCondition.LOCATION().apply { setObjectId(it.id)})
                    }
                    realm.close()
                    objects
                }
                TOURNAMENT_NAME -> {
                    val objects = arrayListOf<QueryCondition.TOURNAMENT_NAME>()
                    val realm = Realm.getDefaultInstance()
                    realm.where<TournamentName>().findAll().forEach {
                        objects.add(QueryCondition.TOURNAMENT_NAME().apply { setObjectId(it.id)})
                    }
                    realm.close()
                    objects
                }
                TOURNAMENT_FEATURE -> {
                    val objects = arrayListOf<QueryCondition.ANY_TOURNAMENT_FEATURES>()
                    val realm = Realm.getDefaultInstance()
                    realm.where<TournamentFeature>().findAll().forEach {
                        objects.add(QueryCondition.ANY_TOURNAMENT_FEATURES().apply { setObjectId(it.id)})
                    }
                    realm.close()
                    objects
                }
                LIMIT_TYPE -> {
                    val limits = arrayListOf<QueryCondition.LIMIT>()
                    val realm = Realm.getDefaultInstance()
                    val distinctLimitTypes = realm.where<Session>().distinct("limit").findAll().sort("limit", Sort.ASCENDING)

                    distinctLimitTypes.forEach { session ->
                        session.limit?.let { limitType->
                            limits.add(QueryCondition.LIMIT().apply { setLimitType(limitType) })
                        }
                    }
                    realm.close()
                    limits
                }
                TABLE_SIZE -> {
                    val tableSizes = arrayListOf<QueryCondition.TABLE_SIZE>()
                    val realm = Realm.getDefaultInstance()
                    val distinctTableSizes =
                        realm.where<Session>().distinct("tableSize").findAll().sort("tableSize", Sort.ASCENDING)
                    distinctTableSizes.forEach { session ->
                        session.tableSize?.let { tableSize ->
                            tableSizes.add(QueryCondition.TABLE_SIZE().apply { setNumberOfPlayer(tableSize) })
                        }
                    }
                    realm.close()
                    tableSizes
                }
                TOURNAMENT_TYPE -> {
                    val tableSizes = arrayListOf<QueryCondition.TOURNAMENT_TYPE>()
                    val realm = Realm.getDefaultInstance()
                    val distinctTournamentTypes =
                        realm.where<Session>().distinct("tournamentType").findAll().sort("tournamentType", Sort.ASCENDING)
                    distinctTournamentTypes.forEach { session ->
                        session.tournamentType?.let { tournamentType ->
                            tableSizes.add(QueryCondition.TOURNAMENT_TYPE().apply { setTournamentType(tournamentType) })
                        }
                    }
                    realm.close()
                    tableSizes
                }
                else -> throw PokerAnalyticsException.QueryTypeUnhandled
            }
		}

}

fun List<Comparator>.combined(): List<List<QueryCondition>> {
	val comparatorList = ArrayList<List<QueryCondition>>()
	this.forEach {
		comparatorList.add(it.queryConditions)
	}
	return getCombinations(comparatorList)
}

inline fun <reified T : Filterable> List<QueryCondition>.query(realm: Realm): RealmQuery<T> {
	var realmQuery = realm.where<T>()
	this.forEach {
		realmQuery = it.filter(realmQuery)
	}
	return realmQuery
}

fun <T> getCombinations(lists: List<List<T>>): List<List<T>> {
	var combinations: MutableSet<List<T>> = LinkedHashSet()
	var newCombinations: MutableSet<List<T>>

	var index = 0

	// extract each of the integers in the first list
	// and add each to ints as a new list
	if (lists.isNotEmpty()) {
		for (i in lists[0]) {
			val newList = ArrayList<T>()
			newList.add(i)
			combinations.add(newList)
		}
		index++
	}
	while (index < lists.size) {
		val nextList = lists[index]
		newCombinations = LinkedHashSet()
		for (first in combinations) {
			for (second in nextList) {
				val newList = ArrayList<T>()
				newList.addAll(first)
				newList.add(second)
				newCombinations.add(newList)
			}
		}
		combinations = newCombinations

		index++
	}

	return combinations.toList()
}