package net.pokeranalytics.android.model.filter

import io.realm.RealmQuery
import io.realm.internal.Table
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.realm.FilterCondition
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.util.extensions.endOfDay
import net.pokeranalytics.android.util.extensions.startOfDay
import java.util.*

fun List<QueryCondition>.name() : String {
    return this.map { it.name }.joinToString(" / ")
}

/**
 * Enum describing the way a query should be handled
 * Some queries requires a value to be checked upon through equals, in, more, less, between
 * To handle that, the enum has a public [valueMap] variable
 * A new type should also set the expected numericValues required in the [filterValuesExpectedKeys]
 */

sealed class QueryCondition(var operator: Operator? = null) {

    abstract class QueryDataCondition : QueryCondition()
        fun setObjectId(id:String) {
            this.valueMap = mapOf("ids" to id)
        }

    companion object {
        fun valueOf(name:String) : QueryCondition {
            val kClass = Class.forName("${QueryCondition::class.qualifiedName}$$name").kotlin
            val instance = kClass.objectInstance ?: kClass.java.newInstance()
            return instance as QueryCondition
        }
    }

    val name: String = this::class.simpleName ?: throw PokerAnalyticsException.FilterElementUnknownName

    enum class Operator {
        BETWEEN,
        MORE,
        LESS;
    }

    object LIVE : QueryCondition()
    object CASH : QueryCondition()
    object ONLINE : QueryCondition()
    object TOURNAMENT: QueryCondition()
    class BANKROLL: QueryDataCondition()
    class GAME: QueryCondition()
    class TOURNAMENT_NAME: QueryCondition()
    class ANY_TOURNAMENT_FEATURES: QueryCondition()
    class ALL_TOURNAMENT_FEATURES: QueryCondition()
    class LOCATION: QueryCondition()
    class LIMIT: QueryCondition() {
        fun setLimitType(limitType: Int) {
            this.valueMap = mapOf("values" to limitType)
        }
    }

    class TABLE_SIZE: QueryCondition() {
        fun setNumberOfPlayer(numberOfPlayer: Int) {
            this.valueMap = mapOf("values" to numberOfPlayer)
        }
    }

    class TOURNAMENT_TYPE: QueryCondition() {
        fun setTournamentType(tournamentType:Int) {
            this.valueMap = mapOf("values" to tournamentType)
        }
    }

    class BLIND: QueryCondition() {
        fun setBlind(blind:String, hasDefaultCurrency:Boolean) {
            this.valueMap = mapOf(
                "blinds" to blind,
                "hasDefaultCurrency" to hasDefaultCurrency)
        }
    }
    class LAST_GAMES: QueryCondition()
    class LAST_SESSIONS: QueryCondition()
    class MORE_NUMBER_OF_TABLE: QueryCondition(Operator.MORE)
    class LESS_NUMBER_OF_TABLE: QueryCondition(Operator.LESS)
    class BETWEEN_NUMBER_OF_TABLE: QueryCondition(Operator.BETWEEN)
    class MORE_THAN_NET_RESULT: QueryCondition(Operator.MORE)
    class LESS_THAN_NET_RESULT: QueryCondition(Operator.LESS)
    class MORE_THAN_BUY_IN: QueryCondition(Operator.MORE)
    class LESS_THAN_BUY_IN: QueryCondition(Operator.LESS)
    class MORE_THAN_CASH_OUT: QueryCondition(Operator.MORE)
    class LESS_THAN_CASH_OUT: QueryCondition(Operator.LESS)
    class MORE_THAN_TIPS: QueryCondition(Operator.MORE)
    class LESS_THAN_TIPS: QueryCondition(Operator.LESS)
    class MORE_THAN_NUMBER_OF_PLAYER: QueryCondition(Operator.MORE)
    class LESS_THAN_NUMBER_OF_PLAYER: QueryCondition(Operator.LESS)
    class BETWEEN_NUMBER_OF_PLAYER: QueryCondition(Operator.BETWEEN)
    class MORE_THAN_TOURNAMENT_FEE: QueryCondition(Operator.MORE)
    class LESS_THAN_TOURNAMENT_FEE: QueryCondition(Operator.LESS)
    class BETWEEN_TOURNAMENT_FEE: QueryCondition(Operator.BETWEEN)
    class MIN_RE_BUY: QueryCondition(Operator.MORE)
    class MAX_RE_BUY: QueryCondition(Operator.LESS)

    // Dates
    class STARTED_FROM_DATE: QueryCondition()
    class STARTED_TO_DATE: QueryCondition()
    class ENDED_FROM_DATE: QueryCondition()
    class ENDED_TO_DATE: QueryCondition()

    class DAY_OF_WEEK: QueryCondition() {
        fun setDay(dayOfWeek:Int) {
            this.valueMap = mapOf("dayOfWeek" to dayOfWeek)
        }
    }

    class MONTH: QueryCondition() {
        fun setMonth(month:Int) {
            this.valueMap = mapOf("month" to month)
        }
    }
    class YEAR: QueryCondition() {
        fun setYear(year:Int) {
            this.valueMap = mapOf("year" to year)
        }
    }
    object WEEK_DAY: QueryCondition()
    object WEEK_END: QueryCondition()
    object TODAY: QueryCondition()
    object YESTERDAY: QueryCondition()
    object TODAY_AND_YESTERDAY: QueryCondition()
    object THIS_WEEK: QueryCondition()
    object THIS_MONTH: QueryCondition()
    object THIS_YEAR: QueryCondition()
    class PAST_DAYS: QueryCondition()
    class MORE_THAN_DURATION: QueryCondition(Operator.MORE)
    class LESS_THAN_DURATION: QueryCondition(Operator.LESS)
    class STARTED_FROM_TIME: QueryCondition()
    class ENDED_TO_TIME: QueryCondition()

    class COMMENT: QueryCondition()


    var valueMap : Map<String, Any?>? = null
        get() {
            this.filterValuesExpectedKeys?.let { valueMapExceptedKeys ->
                field?.let { map ->
                    val missingKeys = map.keys.filter { !valueMapExceptedKeys.contains(it) }
                    if (map.keys.size == valueMapExceptedKeys.size && missingKeys.isNotEmpty()) {
                        throw PokerAnalyticsException.QueryValueMapMissingKeys(missingKeys)
                    }
                } ?: run {
                    throw PokerAnalyticsException.QueryValueMapUnexpectedValue
                }
            }
            return field
        }
        protected set

    private val filterValuesExpectedKeys : Array<String>?
        get() {
            this.operator?.let {
                return when (it) {
                    Operator.BETWEEN -> arrayOf("leftValue", "rightValue")
                    else -> arrayOf("value")
                }
            }
            return when (this) {
                is BANKROLL, is GAME, is LOCATION, is ANY_TOURNAMENT_FEATURES, is ALL_TOURNAMENT_FEATURES, is TOURNAMENT_NAME -> arrayOf("ids")
                is LIMIT, is TOURNAMENT_TYPE, is TABLE_SIZE -> arrayOf("values")
                is BLIND -> arrayOf("blinds", "hasDefaultCurrency")
                is STARTED_FROM_DATE, is STARTED_TO_DATE, is ENDED_FROM_DATE, is ENDED_TO_DATE -> arrayOf("date")
                is DAY_OF_WEEK -> arrayOf("dayOfWeek")
                is MONTH -> arrayOf("month")
                is YEAR -> arrayOf("year")
                else -> null
            }
        }

    /**
     * main method of the enum
     * providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery]
     */
    inline fun <reified T : Filterable> filter(realmQuery: RealmQuery<T>): RealmQuery<T> {
        val fieldName = FilterHelper.fieldNameForQueryType<T>(this)
        fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
        when (operator) {
            Operator.LESS -> {
                val value: Double by valueMap
                return realmQuery.lessThanOrEqualTo(fieldName, value)
            }
            Operator.MORE -> {
                val value: Double by valueMap
                return realmQuery.greaterThanOrEqualTo(fieldName, value)
            }
            Operator.BETWEEN -> {
                val leftValue: Double by valueMap
                val rightValue: Double by valueMap
                return realmQuery.between(fieldName, leftValue, rightValue)
            }
        }

        return when (this) {
            LIVE, ONLINE -> realmQuery.equalTo(fieldName, this == LIVE)
            CASH -> realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal)
            TOURNAMENT -> realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal)
            is ALL_TOURNAMENT_FEATURES -> {
                val ids: Array<String> by valueMap
                ids.forEach {
                    realmQuery.equalTo(fieldName, it)
                }
                realmQuery
            }
            is ANY_TOURNAMENT_FEATURES -> {
                val ids: Array<String> by valueMap
                realmQuery.`in`(fieldName, ids)
            }
            is BLIND -> {
                val blinds: Array<String> by valueMap
                val hasDefaultCurrency: Array<Boolean> by valueMap
                //realmQuery.`in`(fieldName, blinds)
                blinds.forEachIndexed { index, s ->
                    val isUsingDefaultCurrency = hasDefaultCurrency[index]
                    realmQuery.beginGroup()
                    if (isUsingDefaultCurrency) {
                        realmQuery.endsWith(fieldName, s)
                            .and()
                            .isNull("bankroll.currency.code")
                    } else {
                        realmQuery.equalTo(fieldName, s)
                    }
                        .endGroup()
                            if (index < blinds.size - 1) {
                                realmQuery.or()
                            }
                }
                realmQuery
            }
            is BANKROLL, is GAME, is LOCATION, is TOURNAMENT_NAME -> {
                val ids: Array<String> by valueMap
                realmQuery.`in`(fieldName, ids)
            }
            is LIMIT, is TOURNAMENT_TYPE, is TABLE_SIZE -> {
                val values: Array<Int?>? by valueMap
                realmQuery.`in`(fieldName, values)
            }
            is STARTED_FROM_DATE -> {
                val date: Date by valueMap
                realmQuery.greaterThanOrEqualTo(fieldName, date)
            }
            is STARTED_TO_DATE -> {
                val date: Date by valueMap
                realmQuery.lessThanOrEqualTo(fieldName, date)
            }
            is ENDED_FROM_DATE -> {
                val date: Date by valueMap
                realmQuery.greaterThanOrEqualTo(fieldName, date)
            }
            is ENDED_TO_DATE -> {
                val date: Date by valueMap
                realmQuery.lessThanOrEqualTo(fieldName, date)
            }
            is DAY_OF_WEEK -> {
                val dayOfWeek: Int by valueMap
                realmQuery.equalTo(fieldName, dayOfWeek)
            }
            is MONTH -> {
                val month: Int by valueMap
                realmQuery.equalTo(fieldName, month)
            }
            is YEAR -> {
                val year: Int by valueMap
                realmQuery.equalTo(fieldName, year)
            }
            WEEK_END, WEEK_DAY -> {
                var query = realmQuery
                if (this == WEEK_DAY) {
                    query = realmQuery.not()
                }
                query.`in`(fieldName, arrayOf(Calendar.SATURDAY, Calendar.SUNDAY))
            }
            TODAY -> {
                val startDate = Date()
                realmQuery.between(fieldName, startDate.startOfDay(), startDate.endOfDay())
            }
            TODAY_AND_YESTERDAY-> {
                val startDate = Date()
                val calendar = Calendar.getInstance()
                calendar.time = startDate
                calendar.add(Calendar.HOUR_OF_DAY, -24)
                realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
            }
            YESTERDAY -> {
                val calendar = Calendar.getInstance()
                calendar.time = Date()
                calendar.add(Calendar.HOUR_OF_DAY, -24)
                realmQuery.between(fieldName, calendar.time.startOfDay(), calendar.time.endOfDay())
            }
            THIS_WEEK -> {
                val startDate = Date()
                val calendar = Calendar.getInstance()
                calendar.time = startDate
                calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.SUNDAY)
                realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
            }
            THIS_MONTH -> {
                val startDate = Date()
                val calendar = Calendar.getInstance()
                calendar.time = startDate
                calendar.set(Calendar.DAY_OF_MONTH, 1)
                realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
            }
            THIS_YEAR -> {
                val startDate = Date()
                val calendar = Calendar.getInstance()
                calendar.time = startDate
                calendar.set(Calendar.DAY_OF_YEAR, 1)
                realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
            }
            else -> {
                throw PokerAnalyticsException.QueryTypeUnhandled
            }
        }
    }

    fun updateValueMap(filterCondition: FilterCondition) {
        if (filterValuesExpectedKeys == null) {
            return
        }

        this.operator?.let {
            valueMap = mapOf("value" to filterCondition.value)
            return
        }

        when (this) {
            is BANKROLL -> valueMap = mapOf("ids" to filterCondition.ids)
            is ALL_TOURNAMENT_FEATURES, is ANY_TOURNAMENT_FEATURES, is GAME, is LOCATION, is TOURNAMENT_NAME -> {
                valueMap = mapOf("ids" to filterCondition.ids)
            }
            is LIMIT, is TOURNAMENT_TYPE, is TABLE_SIZE -> {
                valueMap = mapOf("values" to filterCondition.values)
            }
            is BLIND -> {
                valueMap = mapOf(
                    "blinds" to filterCondition.blinds,
                    "hasDefaultCurrency" to filterCondition.hasDefaultCurrency)
            }
            is STARTED_FROM_DATE, is STARTED_TO_DATE, is ENDED_FROM_DATE, is ENDED_TO_DATE -> {
                valueMap = mapOf("date" to filterCondition.date)
            }
            is DAY_OF_WEEK -> {
                valueMap = mapOf("dayOfWeek" to filterCondition.dayOfWeek)
            }
            is MONTH -> {
                valueMap = mapOf("month" to filterCondition.month)
            }
            is YEAR -> {
                valueMap = mapOf("year" to filterCondition.year)
            }
            else -> {
                throw PokerAnalyticsException.QueryValueMapUnexpectedValue
            }
        }
    }


}