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

import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
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.shape.CircleShape
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.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
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.line.lineChart
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec
import com.patrykandpatrick.vico.compose.component.textComponent
import com.patrykandpatrick.vico.core.chart.decoration.ThresholdLine
import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp
import com.patrykandpatrick.vico.core.component.marker.MarkerComponent
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
import com.patrykandpatrick.vico.core.entry.FloatEntry
import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition
import com.patrykandpatrick.vico.core.scroll.InitialScroll
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.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.AccelViewMode.ACCELERATION
import llc.arma.ble.domain.usecase.AccelViewMode.ANGLE
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.GetAccelerometerMeasureBySerialFlow
import javax.inject.Inject

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

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

    viewModel.setEvent(AccelerometerAccelContract.Event.OnStart(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))

    DisposableEffect(key1 = "ble", effect = {

        onDispose {
            viewModel.setEvent(AccelerometerAccelContract.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
                    )
                }

            }

            Text(
                modifier = Modifier.weight(1f),
                text = accelMode.localized,
                style = MaterialTheme.typography.titleLarge
            )

            IconButton(
                onClick = {
                    viewModel.setEvent(AccelerometerAccelContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
                },
                enabled = true
            ) {
                Icon(
                    imageVector = Icons.Rounded.Refresh,
                    contentDescription = null
                )
            }

        }

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

        Box(modifier = Modifier) {

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

        }

    }

}
    

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

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


        if (state.measureHistory.isEmpty()) {

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

        } else {


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

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

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

            xProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
                when(measurePoint){
                    is Ble.Accelerometer.RealtimePoint.Common ->
                        FloatEntry(index.toFloat(), measurePoint.x )
                    is Ble.Accelerometer.RealtimePoint.Vibration ->
                        FloatEntry(index.toFloat(), measurePoint.value)
                    is Ble.Accelerometer.RealtimePoint.Angle ->
                        FloatEntry(index.toFloat(), measurePoint.x )
                    is Ble.Accelerometer.RealtimePoint.Rotation ->
                        FloatEntry(index.toFloat(), measurePoint.angle )
                }
            })

            yProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
                when(measurePoint){
                    is Ble.Accelerometer.RealtimePoint.Common ->
                        FloatEntry(index.toFloat(), measurePoint.y )
                    is Ble.Accelerometer.RealtimePoint.Vibration ->
                        FloatEntry(index.toFloat(), measurePoint.value)
                    is Ble.Accelerometer.RealtimePoint.Angle ->
                        FloatEntry(index.toFloat(), measurePoint.y)
                    is Ble.Accelerometer.RealtimePoint.Rotation ->
                        FloatEntry(index.toFloat(), measurePoint.tmp)
                }
            })

            zProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
                when(measurePoint){
                    is Ble.Accelerometer.RealtimePoint.Common ->
                        FloatEntry(index.toFloat(), measurePoint.z)
                    is Ble.Accelerometer.RealtimePoint.Vibration ->
                        FloatEntry(index.toFloat(), measurePoint.value)
                    is Ble.Accelerometer.RealtimePoint.Angle ->
                        FloatEntry(index.toFloat(), measurePoint.z)
                    is Ble.Accelerometer.RealtimePoint.Rotation ->
                        FloatEntry(index.toFloat(), measurePoint.turnovers.toFloat())
                }
            })

            val lineChart = lineChart(
                decorations = listOf(
                    ThresholdLine(
                        thresholdValue = 0f
                    )
                ),
                persistentMarkers = mapOf(xProducer.getModel().maxX to MarkerComponent(
                    label = textComponent(),
                    indicator = null,
                    guideline = axisGuidelineComponent()
                )),
            )

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

            val lastMeasure = state.measureHistory.lastOrNull()


            when(lastMeasure){
                is Ble.Accelerometer.RealtimePoint.Angle -> {
                    Column(
                        verticalArrangement = Arrangement.spacedBy(16.dp),
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {

                        Column(
                            modifier = Modifier.weight(1f),
                            horizontalAlignment = Alignment.CenterHorizontally
                        ) {

                            Text(text = "Ось X: ${lastMeasure.x}")

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

                            Angle(
                                angle = lastMeasure.x,
                                modifier = Modifier.weight(1f),
                            )

                        }

                        Column(
                            modifier = Modifier.weight(1f),
                            horizontalAlignment = Alignment.CenterHorizontally
                        ) {

                            Text(text = "Ось Y: ${lastMeasure.y}")

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

                            Angle(
                                modifier = Modifier.weight(1f),
                                angle = lastMeasure.y
                            )

                        }

                        Column(
                            modifier = Modifier.weight(1f),
                            horizontalAlignment = Alignment.CenterHorizontally
                        ) {

                            Text(text = "Ось Z: ${lastMeasure.z}")

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

                            Angle(
                                modifier = Modifier.weight(1f),
                                angle = lastMeasure.z
                            )

                        }

                    }
                }
                is Ble.Accelerometer.RealtimePoint.Rotation -> {
                    Column(
                        verticalArrangement = Arrangement.spacedBy(16.dp),
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {

                        Column(
                            modifier = Modifier.weight(1f),
                            horizontalAlignment = Alignment.CenterHorizontally
                        ) {

                            Text(text = "Положение: ${lastMeasure.angle}")

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

                            Angle(
                                angle = lastMeasure.angle,
                                modifier = Modifier.weight(1f),
                            )

                        }

                        Text(text = "Скорость:")
                        Chart(
                            marker = marker,
                            chart = lineChart,
                            chartModelProducer = yProducer,
                            startAxis = startAxis(),
                            bottomAxis = bottomAxis(),
                            modifier = Modifier
                                .fillMaxWidth()
                                .weight(1f),
                            autoScaleUp = AutoScaleUp.None,
                            diffAnimationSpec = tween(0),
                            chartScrollSpec = rememberChartScrollSpec(
                                initialScroll = InitialScroll.End,
                                autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
                                autoScrollAnimationSpec = tween(0)
                            )
                        )

                        Text(text = "Обороты:")
                        Chart(
                            marker = marker,
                            chart = lineChart,
                            chartModelProducer = zProducer,
                            startAxis = startAxis(),
                            bottomAxis = bottomAxis(),
                            modifier = Modifier
                                .fillMaxWidth()
                                .weight(1f),
                            autoScaleUp = AutoScaleUp.None,
                            diffAnimationSpec = tween(0),
                            chartScrollSpec = rememberChartScrollSpec(
                                initialScroll = InitialScroll.End,
                                autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
                                autoScrollAnimationSpec = tween(0)
                            )
                        )

                    }
                }
                is Ble.Accelerometer.RealtimePoint.Vibration -> {
                    Column {

                        Text(text = "Вибрация:")

                        Chart(
                            marker = marker,
                            chart = lineChart,
                            chartModelProducer = xProducer,
                            startAxis = startAxis(),
                            bottomAxis = bottomAxis(),
                            modifier = Modifier
                                .fillMaxWidth()
                                .weight(1f),
                            autoScaleUp = AutoScaleUp.None,
                            diffAnimationSpec = tween(0),
                            chartScrollSpec = rememberChartScrollSpec(
                                initialScroll = InitialScroll.End,
                                autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
                                autoScrollAnimationSpec = tween(0)
                            )
                        )

                    }
                }
                is Ble.Accelerometer.RealtimePoint.Common -> {
                    Column() {

                        Text(text = "Ось X:")

                        Chart(
                            marker = marker,
                            chart = lineChart,
                            chartModelProducer = xProducer,
                            startAxis = startAxis(),
                            bottomAxis = bottomAxis(),
                            modifier = Modifier
                                .fillMaxWidth()
                                .weight(1f),
                            autoScaleUp = AutoScaleUp.None,
                            diffAnimationSpec = tween(0),
                            chartScrollSpec = rememberChartScrollSpec(
                                initialScroll = InitialScroll.End,
                                autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
                                autoScrollAnimationSpec = tween(0)
                            )
                        )

                        Text(text = "Ось Y:")
                        Chart(
                            marker = marker,
                            chart = lineChart,
                            chartModelProducer = yProducer,
                            startAxis = startAxis(),
                            bottomAxis = bottomAxis(),
                            modifier = Modifier
                                .fillMaxWidth()
                                .weight(1f),
                            autoScaleUp = AutoScaleUp.None,
                            diffAnimationSpec = tween(0),
                            chartScrollSpec = rememberChartScrollSpec(
                                initialScroll = InitialScroll.End,
                                autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
                                autoScrollAnimationSpec = tween(0)
                            )
                        )

                        Text(text = "Ось Z:")
                        Chart(
                            marker = marker,
                            chart = lineChart,
                            chartModelProducer = zProducer,
                            startAxis = startAxis(),
                            bottomAxis = bottomAxis(),
                            modifier = Modifier
                                .fillMaxWidth()
                                .weight(1f),
                            autoScaleUp = AutoScaleUp.None,
                            diffAnimationSpec = tween(0),
                            chartScrollSpec = rememberChartScrollSpec(
                                initialScroll = InitialScroll.End,
                                autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
                                autoScrollAnimationSpec = tween(0)
                            )
                        )

                    }
                }
                null -> {}
            }

        }

    }
}

@Composable
fun Angle(
    modifier: Modifier = Modifier,
    angle: Float
) {

    Surface(
        modifier = Modifier.fillMaxWidth(),
        shape = CircleShape,
        color = MaterialTheme.colorScheme.primaryContainer
    ) {

        Column(
            modifier = modifier.padding(4.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {

            Text(text = "0°C")

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

                Text(text = "-90°C")

                Surface(
                    modifier = Modifier
                        .aspectRatio(1f)
                        .fillMaxHeight()
                        .padding(8.dp),
                    color = MaterialTheme.colorScheme.secondary,
                    shape = CircleShape
                ) {

                    Box(
                        modifier = Modifier,
                        contentAlignment = Alignment.Center
                    ) {

                        Row(
                            modifier = Modifier
                                .fillMaxWidth()
                                .rotate(-90f + angle),
                            horizontalArrangement = Arrangement.End
                        ) {

                            Box(modifier = Modifier.weight(1f))

                            Divider(modifier = Modifier.weight(1f))

                        }

                    }

                }

                Text(text = "90°C")

            }

            Text(text = "±180°C")

        }

    }

}

@Composable
private fun Exception(

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

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

    }

}

class AccelerometerAccelContract {

    sealed class Event : ViewEvent {

        object StopMeasure : Event()

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

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

    }

    sealed class State : ViewState {

        data class Display(
            val mode: AccelViewMode,
            val measureHistory : List<Ble.Accelerometer.RealtimePoint>
        ) : State()

        object Exception : State()

    }

    sealed class Effect : ViewSideEffect {

    }

}



@HiltViewModel
class AccelerometerAccelViewModel @Inject constructor(
    private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow,
) : BaseViewModel<AccelerometerAccelContract.State, AccelerometerAccelContract.Event, AccelerometerAccelContract.Effect>() {

    var measureJob: Job? = null

    private var lastSerial: String? = null

    override fun setInitialState() = AccelerometerAccelContract.State.Display(
        mode = ACCELERATION,
        measureHistory = emptyList()
    )

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

    private fun reduce(
        state: AccelerometerAccelContract.State,
        event: AccelerometerAccelContract.Event.StopMeasure
    ) {

        measureJob?.cancel()
        measureJob = null

        setState {
            AccelerometerAccelContract.State.Display(
                mode = ACCELERATION,
                measureHistory = emptyList()
            )
        }

    }

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

        startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, false)

    }

    private fun reduce(
        state: AccelerometerAccelContract.State,
        event: AccelerometerAccelContract.Event.OnRefreshHistory
    ) {
        startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, true)
    }

    private fun startReadMeasure(
        serial: String,
        accelScale: AccelScale,
        accelMode: AccelViewMode,
        fftAxis: FftAxis,
        fftMode: FftViewMode,
        frequency: FftFrequency,
        restartJob: Boolean
    ){

        if(restartJob || measureJob == null) {
            measureJob?.cancel()
            measureJob = null
            measureJob = viewModelScope.launch {

                setState {
                    AccelerometerAccelContract.State.Display(
                        mode = ACCELERATION,
                        measureHistory = emptyList()
                    )
                }

                getAccelerometerMeasureBySerialFlow(serial, accelScale, accelMode, fftAxis, fftMode, frequency).onEach {
                    it.fold(
                        onSuccess = {
                            setState {
                                when (this) {
                                    is AccelerometerAccelContract.State.Display -> {
                                        var dataList = this.measureHistory.toMutableList().apply {
                                            add(it)
                                        }
                                        if(accelMode != ANGLE) {
                                            dataList = dataList.takeLast(100).toMutableList()
                                        }
                                        AccelerometerAccelContract.State.Display(accelMode, dataList)
                                    }

                                    AccelerometerAccelContract.State.Exception -> {
                                        AccelerometerAccelContract.State.Display(accelMode, listOf(it))
                                    }
                                }
                            }
                        },
                        onFailure = {
                            setState {
                                AccelerometerAccelContract.State.Exception
                            }
                        }
                    )
                }.launchIn(this)

            }
        }
    }

}