package llc.arma.ble.app.ui.screen.inspection.accelerometer.view

import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewModelScope
import com.patrykandpatrick.vico.compose.axis.axisGuidelineComponent
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
import com.patrykandpatrick.vico.compose.axis.vertical.startAxis
import com.patrykandpatrick.vico.compose.chart.Chart
import com.patrykandpatrick.vico.compose.chart.column.columnChart
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollState
import com.patrykandpatrick.vico.compose.component.textComponent
import com.patrykandpatrick.vico.core.axis.AxisPosition
import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter
import com.patrykandpatrick.vico.core.component.marker.MarkerComponent
import com.patrykandpatrick.vico.core.entry.ChartEntry
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
import com.patrykandpatrick.vico.core.entry.FloatEntry
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import llc.arma.ble.app.ui.common.BaseViewModel
import llc.arma.ble.app.ui.common.ViewEvent
import llc.arma.ble.app.ui.common.ViewSideEffect
import llc.arma.ble.app.ui.common.ViewState
import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.model.BleInfo
import llc.arma.ble.domain.usecase.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode
import llc.arma.ble.domain.usecase.FftAxis
import llc.arma.ble.domain.usecase.FftFrequency
import llc.arma.ble.domain.usecase.FftViewMode
import llc.arma.ble.domain.usecase.GetAccelerometerSpectreBySerial
import javax.inject.Inject

class AccelerometerEntry(
    val frequency: Long,
    override val x: Float,
    override val y: Float,
) : ChartEntry {

    override fun withY(y: Float) = AccelerometerEntry(frequency, x, y)

}

@Composable
fun AccelerometerSpectre(
    ble: BleInfo,
    accelMode: AccelViewMode,
    fftAxis: FftAxis,
    fftMode: FftViewMode,
    frequency: FftFrequency,
    accelScale: AccelScale,
    onDismiss: (() -> Unit)? = null
) {

    val viewModel = hiltViewModel<AccelerometerSpectreViewModel>()
    val state = viewModel.viewState.value

    LaunchedEffect(ble.serial, accelMode, fftAxis, fftMode, frequency) {
        viewModel.setEvent(AccelerometerSpectreContract.Event.OnStart(ble.serial, accelMode, fftAxis, fftMode, frequency, accelScale))
    }

    DisposableEffect(key1 = "ble", effect = {

        onDispose {
            viewModel.setEvent(AccelerometerSpectreContract.Event.StopMeasure)
        }

    })

    Column(
        modifier = Modifier.fillMaxHeight(0.9f)
    ) {

        Row(
            modifier = Modifier.padding(horizontal = 12.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {

            onDismiss?.let {

                IconButton(onClick = it) {
                    Icon(
                        imageVector = Icons.Rounded.ArrowBack,
                        contentDescription = null
                    )
                }

            }

            val title = when(state){
                is AccelerometerSpectreContract.State.Display -> {
                    if (state.previousHistory !== null) {
                        "${fftMode.localized} (${state.previousHistory.size})"
                    }else {
                        fftMode.localized
                    }
                }
                AccelerometerSpectreContract.State.Exception -> fftMode.localized
            }

            Row(
                modifier = Modifier.weight(1f),
                verticalAlignment = Alignment.CenterVertically
            ) {

                Text(
                    modifier = Modifier.padding(end = 16.dp),
                    text = title,
                    style = MaterialTheme.typography.titleLarge
                )

                if(state is AccelerometerSpectreContract.State.Display) {

                    when (state.loadingHistoryState) {
                        is ProgressState.Indeterminate -> {

                            CircularProgressIndicator(
                                modifier = Modifier.size(16.dp),
                                strokeWidth = 2.dp,
                                strokeCap = StrokeCap.Round,
                            )

                        }
                        is ProgressState.Progress -> {

                            val progressAnimDuration = 1500
                            val progressAnimation by animateFloatAsState(
                                targetValue = state.loadingHistoryState.value,
                                animationSpec = tween(
                                    durationMillis = progressAnimDuration,
                                    easing = FastOutSlowInEasing
                                )
                            )

                            CircularProgressIndicator(
                                modifier = Modifier.size(16.dp),
                                strokeWidth = 2.dp,
                                strokeCap = StrokeCap.Round,
                                progress = progressAnimation,
                            )

                        }
                        else -> {}
                    }

                }

            }

            IconButton(
                onClick = {
                    viewModel.setEvent(AccelerometerSpectreContract.Event.OnStart(ble.serial, accelMode, fftAxis, fftMode, frequency, accelScale))
                },
                enabled = when(state){
                    is AccelerometerSpectreContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
                    AccelerometerSpectreContract.State.Exception -> true
                }
            ) {
                Icon(
                    imageVector = Icons.Rounded.Refresh,
                    contentDescription = null
                )
            }

        }

        Spacer(modifier = Modifier.height(16.dp))

        Box(modifier = Modifier) {

            when (state) {
                is AccelerometerSpectreContract.State.Display -> Display(state = state)
                AccelerometerSpectreContract.State.Exception -> Exception()
            }

        }

    }

}

val axisValueFormatter = AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
    (chartValues.chartEntryModel.entries.firstOrNull()
        ?.getOrNull(value.toInt()) as? AccelerometerEntry)
        ?.frequency?.let { String.format("%.1f", (it.toFloat() / 256f)) }
        .orEmpty()
}


@Composable
fun Display(
    state: AccelerometerSpectreContract.State.Display
) {

    Box(modifier = Modifier
        .padding(8.dp)
        .fillMaxSize()
    ) {

        val data = if(state.loadingHistoryState is ProgressState.Finished){
            state.loadingHistoryState.data
        } else {
            state.previousHistory
        }

        val producer = remember {
            ChartEntryModelProducer(listOf<FloatEntry>())
        }

        if(data != null){

            if(data.isEmpty()){

                Text(
                    modifier = Modifier.align(Alignment.Center),
                    text = "Нет данных"
                )

            } else {

                LaunchedEffect(data){
                    producer.setEntries(
                        data.mapIndexed { index, measurePoint ->
                            AccelerometerEntry(measurePoint.frequency, index.toFloat(), measurePoint.value)

                        }
                    )
                }

                val lineChart = columnChart(
                    spacing = 1.5.dp,
                    persistentMarkers = mapOf(producer.getModel().maxX to MarkerComponent(
                        label = textComponent(),
                        indicator = null,
                        guideline = axisGuidelineComponent()
                    )),
                )

                val scrollState = rememberChartScrollState()

                val marker = MarkerComponent(
                    label = textComponent(),
                    indicator = null,
                    guideline = axisGuidelineComponent()
                )

                Chart(
                    marker = marker,
                    diffAnimationSpec = tween(0),
                    isZoomEnabled = true,
                    chartScrollState = scrollState,
                    chart = lineChart,
                    chartModelProducer = producer,
                    startAxis = startAxis(),
                    bottomAxis = bottomAxis(
                        tickLength = 0.dp,
                        valueFormatter = axisValueFormatter,
                        labelRotationDegrees = -90f,
                    ),
                    modifier = Modifier.fillMaxSize()
                )
                

            }

        } else {
            when (state.loadingHistoryState) {
                is ProgressState.Indeterminate -> {

                    CircularProgressIndicator(
                        strokeCap = StrokeCap.Round,
                        modifier = Modifier.align(Alignment.Center)
                    )

                }
                is ProgressState.Progress -> {

                    val progressAnimDuration = 1500
                    val progressAnimation by animateFloatAsState(
                        targetValue = state.loadingHistoryState.value,
                        animationSpec = tween(
                            durationMillis = progressAnimDuration,
                            easing = FastOutSlowInEasing
                        )
                    )

                    CircularProgressIndicator(
                        strokeCap = StrokeCap.Round,
                        progress = progressAnimation,
                        modifier = Modifier.align(Alignment.Center)
                    )

                }
                else -> {}
            }
        }

    }
}

@Composable
private fun Exception(

) {
    Box(
        modifier = Modifier
            .padding(8.dp)
            .fillMaxWidth()
            .aspectRatio(2f),
    ){

        Text(
            textAlign = TextAlign.Center,
            text = "Во время загрузки произошла ошибка",
            modifier = Modifier.align(Alignment.Center)
        )

    }

}

class AccelerometerSpectreContract {

    sealed class Event : ViewEvent {

        object StopMeasure : Event()

        data class OnStart(
            val serial: String,
            val accelMode: AccelViewMode,
            val fftAxis: FftAxis,
            val fftMode: FftViewMode,
            val frequency: FftFrequency,
            val accelScale: AccelScale
        ) : Event()

        data class OnRefreshHistory(
            val serial: String,
            val accelMode: AccelViewMode,
            val fftAxis: FftAxis,
            val fftMode: FftViewMode,
            val frequency: FftFrequency,
            val accelScale: AccelScale
        ) : Event()

    }

    sealed class State : ViewState {

        data class Display(
            val previousHistory : List<Ble.Accelerometer.SpectrePoint>?,
            val loadingHistoryState : ProgressState<List<Ble.Accelerometer.SpectrePoint>>
        ) : State()

        object Exception : State()

    }

    sealed class Effect : ViewSideEffect {

    }

}

@HiltViewModel
class AccelerometerSpectreViewModel @Inject constructor(
    private val getAccelerometerSpectreBySerial: GetAccelerometerSpectreBySerial
) : BaseViewModel<AccelerometerSpectreContract.State, AccelerometerSpectreContract.Event, AccelerometerSpectreContract.Effect>() {

    private var job: Job? = null
    private var lastSerial: String? = null

    override fun setInitialState() = AccelerometerSpectreContract.State.Display(
        loadingHistoryState = ProgressState.Indeterminate,
        previousHistory = null
    )

    override fun handleEvents(event: AccelerometerSpectreContract.Event) {
        when(event){
            is AccelerometerSpectreContract.Event.OnStart -> reduce(viewState.value, event)
            is AccelerometerSpectreContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
            is AccelerometerSpectreContract.Event.StopMeasure -> reduce(viewState.value, event)
        }
    }

    private fun reduce(
        state: AccelerometerSpectreContract.State,
        event: AccelerometerSpectreContract.Event.OnStart
    ) {

        viewModelScope.launch {

            if(state is AccelerometerSpectreContract.State.Display) {

                //if(lastSerial != event.serial) {

                lastSerial = event.serial

                setState {
                    AccelerometerSpectreContract.State.Display(
                        loadingHistoryState = ProgressState.Indeterminate,
                        previousHistory = when(state.loadingHistoryState){
                            is ProgressState.Finished -> state.loadingHistoryState.data
                            is ProgressState.Indeterminate -> null
                            is ProgressState.Progress -> null
                        }
                    )
                }

            } else {
                setState {
                    AccelerometerSpectreContract.State.Display(
                        loadingHistoryState = ProgressState.Indeterminate,
                        previousHistory = null
                    )
                }

            }

            job?.cancel()
            job = getAccelerometerSpectreBySerial(
                serial = event.serial,
                accelMode = event.accelMode,
                fftAxis = event.fftAxis,
                fftMode = event.fftMode,
                frequency = event.frequency,
                accelScale = event.accelScale
            ).onEach {

                val currentState = viewState.value

                if(currentState is AccelerometerSpectreContract.State.Display) {

                    it.fold(
                        onSuccess = {
                            setState {

                                AccelerometerSpectreContract.State.Display(
                                    loadingHistoryState = it,
                                    previousHistory = when (it) {
                                        is ProgressState.Finished -> {
                                            it.data
                                        }
                                        is ProgressState.Indeterminate -> currentState.previousHistory
                                        is ProgressState.Progress -> currentState.previousHistory
                                    }
                                )

                            }
                        },
                        onFailure = {
                            setState {
                                AccelerometerSpectreContract.State.Exception
                            }
                        }
                    )

                }

            }.launchIn(this)

        }

    }

    private fun reduce(
        state: AccelerometerSpectreContract.State,
        event: AccelerometerSpectreContract.Event.OnRefreshHistory
    ) {
        /*viewModelScope.launch {

            setState {
                AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
            }

            getAccelerometerSpectreBySerial(
                serial = event.serial,
                accelMode = event.accelMode,
                fftAxis = event.fftAxis,
                fftMode = event.fftMode,
                frequency = event.frequency,
                accelScale = event.accelScale
            ).onEach {
                it.fold(
                    onSuccess = {
                        setState {
                            AccelerometerHistoryContract.State.Display(it)
                        }
                    },
                    onFailure = {
                        setState {
                            AccelerometerHistoryContract.State.Exception
                        }
                    }
                )
            }.launchIn(this)

        }*/
    }

    private fun reduce(
        state: AccelerometerSpectreContract.State,
        event: AccelerometerSpectreContract.Event.StopMeasure
    ) {
        job?.cancel()
    }

}