package llc.arma.ble.app.ui.screen.rotation.statistic

import android.icu.util.GregorianCalendar
import android.util.Log
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.patrykandpatrick.vico.core.extension.sumOf
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import llc.arma.ble.app.ui.common.BaseViewModel
import llc.arma.ble.domain.usecase.GetRotationsBySerial
import llc.arma.ble.domain.usecase.GetWheelRadiusBySerial
import llc.arma.ble.domain.usecase.SetWheelRadius
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.absoluteValue

fun <T> Collection<T>.splitBy(separator: (T) -> Any?): List<List<T>> {

    val result = mutableListOf<MutableList<T>>()

    var newSublist = true
    for (item in this) {
        newSublist = (separator(item) != result.lastOrNull()?.lastOrNull()?.let { separator(it) })
        if (newSublist || result.lastOrNull() == null)
            result += mutableListOf<T>()
        result.last() += item
    }

    return result
}

@HiltViewModel
class RotationStatisticViewModel @Inject constructor(
    private val setWheelRadius: SetWheelRadius,
    private val getWheelRadius: GetWheelRadiusBySerial,
    private val getRotationsBySerial: GetRotationsBySerial,
    private val savedStateHandle: SavedStateHandle
) : BaseViewModel<RotationStatisticContract.State, RotationStatisticContract.Event, RotationStatisticContract.Effect>() {

    init {

        viewModelScope.launch {

            load()

        }

    }

    override fun setInitialState() = RotationStatisticContract.State.Loading

    override fun handleEvents(event: RotationStatisticContract.Event) {
        when(event){
            is RotationStatisticContract.Event.OnRadiusChanged -> reduce(viewState.value, event)
            is RotationStatisticContract.Event.OnStartDateChanged -> reduce(viewState.value, event)
            is RotationStatisticContract.Event.OnStopDateChanged -> reduce(viewState.value, event)
            is RotationStatisticContract.Event.OnNavigateToDelete -> reduce(viewState.value, event)
            is RotationStatisticContract.Event.OnNavigateUp -> reduce(viewState.value, event)
        }
    }

    private suspend fun load(){
        savedStateHandle.get<String>("serial")?.let {

            val state = viewState.value

            val r = getWheelRadius(it)?.toFloat() ?: 500f
            val rCm = r / 10f
            val circumference = 2 * Math.PI * rCm
            Log.d("circumference", circumference.toString())

            var rotations = getRotationsBySerial(it)

            if(state is RotationStatisticContract.State.Display){
                rotations = rotations.filter {
                    it.date >= (state.startDate ?: 0) && it.date <= (state.endDate ?: Long.MAX_VALUE)
                }
            }

            val split: List<RotationStatisticContract.State.Display.RotationRange> = if(rotations.size < 2) {
                emptyList()
            }else {

                val pointDuration = rotations[1].date - rotations[0].date

                rotations.splitBy {
                    Log.d("rDate", it.date.toString())
                    if (it.rotations > 0) {
                        true
                    } else {
                        if (it.rotations < 0) {
                            false
                        } else {
                            null
                        }
                    }
                }.map { range ->

                    val start = range.minOf { it.date } - pointDuration
                    val end = range.maxOf { it.date }

                    val speeds = range.map {
                        val km = ((it.rotations.absoluteValue / 8f) * circumference) / 100_000f
                        km / (pointDuration.toFloat() / TimeUnit.HOURS.toMillis(1).toFloat())
                    }

                    RotationStatisticContract.State.Display.RotationRange(
                        distance = ((range.sumOf { it.rotations.toFloat() } / 8f) * circumference) / 100f,
                        startDate = start,
                        endDate = end,
                        maxSpeed = speeds.max(),
                        avgSpeed = speeds.average(),
                        rotations = range.sumOf { it.rotations.toFloat() } / 8f
                    )

                }

            }

            setState {

                when(this){
                    is RotationStatisticContract.State.Display -> {
                        copy(
                            rotations = rotations,
                            ranges = split
                        )
                    }
                    RotationStatisticContract.State.Loading -> {
                        RotationStatisticContract.State.Display(
                            rotations = rotations,
                            ranges = split,
                            radius = r.toLong(),
                            startDate = null,
                            endDate = null
                        )
                    }
                }

            }


        } ?: throw IllegalArgumentException()
    }

    private fun reduce(
        state: RotationStatisticContract.State,
        event: RotationStatisticContract.Event.OnRadiusChanged
    ) {

        if(state is RotationStatisticContract.State.Display){

            setState {
                state.copy(radius = event.radius)
            }

            viewModelScope.launch {

                setWheelRadius(savedStateHandle.get<String>("serial")!!, event.radius ?: 100L)

                load()

            }

        }

    }

    private fun reduce(
        state: RotationStatisticContract.State,
        event: RotationStatisticContract.Event.OnStartDateChanged
    ) {

        if(state is RotationStatisticContract.State.Display){

            val mCalendar = GregorianCalendar()
            val mTimeZone = mCalendar.timeZone
            val mGMTOffset: Int = mTimeZone.rawOffset

            val start = event.start?.let {
                TimeUnit.DAYS.toMillis(TimeUnit.MILLISECONDS.toDays(event.start)) - mGMTOffset
            }

            Log.d("date", start.toString() + " " + event.start)

            setState {
                state.copy(startDate = start)
            }

            viewModelScope.launch {

                load()

            }

        }

    }

    private fun reduce(
        state: RotationStatisticContract.State,
        event: RotationStatisticContract.Event.OnStopDateChanged
    ) {

        if(state is RotationStatisticContract.State.Display){

            val mCalendar = GregorianCalendar()
            val mTimeZone = mCalendar.timeZone
            val mGMTOffset: Int = mTimeZone.rawOffset

            val stop = event.stop?.let {
                TimeUnit.DAYS.toMillis(TimeUnit.MILLISECONDS.toDays(event.stop) + 1) - mGMTOffset - 1_000
            }

            setState {
                state.copy(endDate = stop)
            }

            viewModelScope.launch {

                load()

            }

        }

    }

    private fun reduce(
        state: RotationStatisticContract.State,
        event: RotationStatisticContract.Event.OnNavigateUp
    ) {

        setEffect {

            RotationStatisticContract.Effect.Navigation.NavigateUp

        }

    }

    private fun reduce(
        state: RotationStatisticContract.State,
        event: RotationStatisticContract.Event.OnNavigateToDelete
    ) {

        setEffect {

            RotationStatisticContract.Effect.Navigation.NavigateToDelete(savedStateHandle.get<String>("serial")!!)

        }

    }

}