package net.pokeranalytics.android.model

import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.where
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.Criteria.Bankrolls.comparison
import net.pokeranalytics.android.model.Criteria.Blinds.comparison
import net.pokeranalytics.android.model.Criteria.Games.comparison
import net.pokeranalytics.android.model.Criteria.Limits.comparison
import net.pokeranalytics.android.model.Criteria.Locations.comparison
import net.pokeranalytics.android.model.Criteria.TableSizes.comparison
import net.pokeranalytics.android.model.Criteria.TournamentFeatures.comparison
import net.pokeranalytics.android.model.Criteria.TournamentFees.comparison
import net.pokeranalytics.android.model.Criteria.TournamentNames.comparison
import net.pokeranalytics.android.model.Criteria.TournamentTypes.comparison
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.realm.*

fun List<Criteria>.combined(): List<Query> {
    val comparatorList = ArrayList<List<Query>>()
    this.forEach { criteria ->
        comparatorList.add(criteria.queries)
    }
    return getCombinations(comparatorList)
}

fun getCombinations(queries: List<List<Query>>): List<Query> {

    if (queries.size == 0) { return listOf() }

    val mutableQueries = queries.toMutableList()
    var combinations = mutableQueries.removeAt(0)

    for (queryList in mutableQueries) {

        val newCombinations = mutableListOf<Query>()
        combinations.forEach { combinedQuery ->
            queryList.forEach { queryToAdd ->
                val nq = Query().merge(combinedQuery).merge(queryToAdd)
                newCombinations.add(nq)
            }
        }
        combinations = newCombinations
    }

    return combinations
}

//fun getCombinations(lists: List<Query>): List<Query> {
//    var combinations: LinkedHashSet<Query> = LinkedHashSet()
//    var newCombinations: LinkedHashSet<Query>
//
//    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()
//}

sealed class Criteria {
    abstract class RealmCriteria : Criteria() {
        inline fun <reified T: NameManageable> comparison(): List<Query> {
            return compare<QueryCondition.QueryDataCondition<NameManageable>, T>()
        }
    }

    abstract class SimpleCriteria(private val conditions:List<QueryCondition>): Criteria() {
        fun comparison(): List<Query> {
            return conditions.map { Query(it) }
        }
    }

    abstract class ListCriteria : Criteria() {
        inline fun <reified T : QueryCondition.ListOfValues<S>, reified S : Comparable<S>> comparison(): List<Query> {
            QueryCondition.distinct<Session, T, S>()?.let {
                val values = it.mapNotNull { session ->
                    when (this) {
                        is Limits -> if (session.limit is S) { session.limit as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
                        is TournamentTypes ->  if (session.tournamentType is S) { session.tournamentType as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
                        is TableSizes ->  if (session.tableSize is S) { session.tableSize as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
                        is TournamentFees -> if (session.tournamentEntryFee is S) { session.tournamentEntryFee as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
                        is Blinds ->  if (session.blinds is S) { session.blinds as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
                        else -> null
                    }
                }.distinct()
                return compareList<T, S>(values = values)
            }
            return listOf()
        }
    }


    object Bankrolls: RealmCriteria()
    object Games: RealmCriteria()
    object TournamentNames: RealmCriteria()
    object Locations: RealmCriteria()
    object TournamentFeatures: RealmCriteria()
    object Limits: ListCriteria()
    object TableSizes: ListCriteria()
    object TournamentTypes: ListCriteria()
    object MonthsOfYear:  SimpleCriteria(List(12) { index -> QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index)} })
    object DaysOfWeek: SimpleCriteria(List(7) { index -> QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) } })
    object SessionTypes: SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament))
    object BankrollTypes: SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline))
    object DayPeriods: SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd))
    object Years: ListCriteria()
    object AllMonthsUpToNow: ListCriteria()
    object Blinds: ListCriteria()
    object TournamentFees: ListCriteria()
    object Cash: SimpleCriteria(listOf(QueryCondition.IsCash))
    object Tournament: SimpleCriteria(listOf(QueryCondition.IsTournament))

    val queries: List<Query>
        get() {
            return when (this) {
                is AllMonthsUpToNow -> {
                    val realm = Realm.getDefaultInstance()
                    val firstSession= realm.where<Session>().sort("startDate", Sort.ASCENDING).findFirst()
                    val lastSession = realm.where<Session>().sort("startDate", Sort.DESCENDING).findFirst()
                    realm.close()

                    val years: ArrayList<Query> = arrayListOf()

                    val firstYear = firstSession?.year ?: return years
                    val firstMonth = firstSession.month ?: return years
                    val lastYear = lastSession?.year ?: return years
                    val lastMonth = lastSession.month ?: return years

                    for (year in firstYear..lastYear) {
                        val currentYear = QueryCondition.AnyYear(year)
                        for (month in 0..11) {

                            if (year == firstYear && month < firstMonth) {
                                continue
                            }
                            if (year == lastYear && month > lastMonth) {
                                continue
                            }

                            val currentMonth = QueryCondition.AnyMonthOfYear(month)
                            val query = Query(currentYear, currentMonth)
                            years.add(query)
                        }
                    }
                    years
                }
                else -> {
                    return this.queryConditions
                }
            }
        }

    val queryConditions: List<Query>
        get() {
            return when (this) {
                is Bankrolls -> comparison<Bankroll>()
                is Games -> comparison<Game>()
                is TournamentFeatures -> comparison<TournamentFeature>()
                is TournamentNames -> comparison<TournamentName>()
                is Locations -> comparison<Location>()
                is SimpleCriteria -> comparison()
                is Limits -> comparison<QueryCondition.AnyLimit, Int>()
                is TournamentTypes -> comparison<QueryCondition.AnyTournamentType, Int>()
                is TableSizes -> comparison<QueryCondition.AnyTableSize, Int>()
                is TournamentFees -> comparison<QueryCondition.TournamentFee, Double >()
                is Years -> {
                    val years = arrayListOf<Query>()
                    val realm = Realm.getDefaultInstance()
                    val lastSession = realm.where<Session>().sort("startDate", Sort.DESCENDING).findFirst()
                    val yearNow = lastSession?.year ?: return years

                    realm.where<Session>().sort("year", Sort.ASCENDING).findFirst()?.year?.let {
                        for (index in 0..(yearNow - it)) {
                            val yearCondition = QueryCondition.AnyYear().apply {
                                listOfValues = arrayListOf(it + index)
                            }
                            years.add(Query(yearCondition))
                        }
                    }
                    realm.close()
                    years
                }
                is Blinds -> comparison<QueryCondition.AnyBlind, String >()
                else -> throw PokerAnalyticsException.QueryTypeUnhandled
            }
        }

    companion object {
        inline fun <reified S : QueryCondition.QueryDataCondition<NameManageable >, reified T : NameManageable> compare(): List<Query> {
            val objects = mutableListOf<S>()
            val realm = Realm.getDefaultInstance()
            realm.where<T>().findAll().forEach {
                val condition = (QueryCondition.getInstance<T>() as S).apply {
                    setObject(it)
                }
                objects.add(condition)
            }
            objects.sorted()
            realm.close()
            return objects.map { Query(it) }
        }

        inline fun < reified  S : QueryCondition.ListOfValues<T>, T:Any > compareList(values:List<T>): List<Query> {
            val objects = mutableListOf<S>()
            values.forEach {
                val condition =(S::class.java.newInstance()).apply {
                    listOfValues = arrayListOf(it)
                }
                objects.add(condition)
            }
            objects.sorted()
            return objects.map { Query(it) }
        }
    }
}
