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.Spacer
import androidx.compose.foundation.layout.aspectRatio
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.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.CloudUpload
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material.icons.rounded.TableView
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
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.line.lineChart
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec
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.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.ChartEntry
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.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.ExportToXlsx
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.GetAccelerometerHistoryBySerial
import llc.arma.ble.domain.usecase.GetBleBySerial
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import javax.inject.Inject

class AccelEntry(
    val localDate: Long,
    override val x: Float,
    override val y: Float,
) : ChartEntry {

    override fun withY(y: Float) = AccelEntry(localDate, x, y)

}

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

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

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

    /*DisposableEffect("ble") {
        onDispose {
            viewModel.setEvent(AccelerometerHistoryContract.Event.StopMeasure)
        }
    }*/

    Column() {

        TopAppBar(
            navigationIcon = {
                onDismiss?.let {

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

                }
            },
            title = {
                val title = when(state){
                    is AccelerometerHistoryContract.State.Display -> {
                        when (state.loadingHistoryState) {
                            is ProgressState.Finished -> "${accelMode.localized} (${state.loadingHistoryState.data.size})"
                            is ProgressState.Indeterminate -> accelMode.localized
                            is ProgressState.Progress -> accelMode.localized
                        }
                    }
                    AccelerometerHistoryContract.State.Exception -> accelMode.localized
                }

                Text(
                    modifier = Modifier.weight(1f),
                    text = title,
                    style = MaterialTheme.typography.titleLarge
                )
            },
            actions = {

                IconButton(
                    onClick = onShowStatistic,
                    enabled =  when(state){
                        is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
                        AccelerometerHistoryContract.State.Exception -> false
                    }
                ) {
                    Icon(
                        imageVector = Icons.Rounded.TableView,
                        contentDescription = null
                    )
                }

                IconButton(
                    onClick = {
                        viewModel.setEvent(AccelerometerHistoryContract.Event.OnExport)
                    },
                    enabled =  when(state){
                        is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
                        AccelerometerHistoryContract.State.Exception -> false
                    }
                ) {
                    Icon(
                        imageVector = Icons.Rounded.CloudUpload,
                        contentDescription = null
                    )
                }

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

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

        Box(modifier = Modifier) {

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

        }

    }

}

val dayFormatter = SimpleDateFormat("dd", Locale.getDefault())
val dateFormatter = SimpleDateFormat("dd.MM", Locale.getDefault())
val timeFormatter = SimpleDateFormat("HH:mm", Locale.getDefault())
    

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

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

        when (state.loadingHistoryState) {

            is ProgressState.Finished -> {

                if(state.loadingHistoryState.data.isEmpty()){

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

                } else {

                    val axisValueFormatter =
                        AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
                            val first = (chartValues.chartEntryModel.entries.firstOrNull()?.firstOrNull() as? AccelEntry)
                            val last = (chartValues.chartEntryModel.entries.firstOrNull()?.lastOrNull() as? AccelEntry)
                            val previous = (chartValues.chartEntryModel.entries.firstOrNull()?.getOrNull(value.toInt() - 1) as? AccelEntry)
                            val current = (chartValues.chartEntryModel.entries.firstOrNull()?.getOrNull(value.toInt()) as? AccelEntry)

                            if(current != null) {
                                if (first == current || last == current) {
                                    dateFormatter.format(Date(current.localDate))
                                } else {
                                    if(previous != null && dayFormatter.format(previous.localDate) != dayFormatter.format(current.localDate)){
                                        dateFormatter.format(Date(current.localDate))
                                    }else{
                                        timeFormatter.format(Date(current.localDate))
                                    }
                                }.orEmpty()
                            } else {
                                " "
                            }

                            /*(chartValues.chartEntryModel.entries.firstOrNull()
                                ?.getOrNull(value.toInt()) as? AccelEntry)
                                ?.localDate
                                ?.let {
                                    if(first)
                                    formatter.format(Date(it))
                                }
                                .orEmpty()*/
                        }

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

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

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

                    xProducer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
                        when(measurePoint){
                            is Ble.Accelerometer.HistoryPoint.Acceleration -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.x )
                            }
                            is Ble.Accelerometer.HistoryPoint.Vibration -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value)
                            }
                            is Ble.Accelerometer.HistoryPoint.Angle -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.x )
                            }
                            is Ble.Accelerometer.HistoryPoint.Rotation -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value.toFloat() )
                            }
                        }

                    })

                    yProducer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
                        when(measurePoint){
                            is Ble.Accelerometer.HistoryPoint.Acceleration -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.y )
                            }
                            is Ble.Accelerometer.HistoryPoint.Vibration -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value)
                            }
                            is Ble.Accelerometer.HistoryPoint.Angle -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.y )
                            }
                            is Ble.Accelerometer.HistoryPoint.Rotation -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value.toFloat() )
                            }
                        }
                    })

                    zProducer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
                        when(measurePoint){
                            is Ble.Accelerometer.HistoryPoint.Acceleration -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.z )
                            }
                            is Ble.Accelerometer.HistoryPoint.Vibration -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value)
                            }
                            is Ble.Accelerometer.HistoryPoint.Angle -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.z )
                            }
                            is Ble.Accelerometer.HistoryPoint.Rotation -> {
                                AccelEntry(measurePoint.date, index.toFloat(), measurePoint.value.toFloat() )
                            }
                        }
                    })

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

                    val lastMeasure = state.loadingHistoryState.data.lastOrNull()

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

                    when(lastMeasure){
                        is Ble.Accelerometer.HistoryPoint.Acceleration,
                        is Ble.Accelerometer.HistoryPoint.Angle -> {
                            Column {

                                Text(text = "Ось X:")
                                Chart(
                                    marker = marker,
                                    chart = lineChart,
                                    chartModelProducer = xProducer,
                                    startAxis = startAxis(),
                                    bottomAxis = bottomAxis(
                                        tickLength = 0.dp,
                                        valueFormatter = axisValueFormatter,
                                        labelRotationDegrees = -90f,
                                    ),
                                    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(
                                        tickLength = 0.dp,
                                        valueFormatter = axisValueFormatter,
                                        labelRotationDegrees = -90f,
                                    ),
                                    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(
                                        tickLength = 0.dp,
                                        valueFormatter = axisValueFormatter,
                                        labelRotationDegrees = -90f,
                                    ),
                                    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.HistoryPoint.Rotation -> {
                            Column {

                                Text(text = "Обороты:")

                                Chart(
                                    marker = marker,
                                    chart = lineChart,
                                    chartModelProducer = xProducer,
                                    startAxis = startAxis(),
                                    bottomAxis = bottomAxis(
                                        tickLength = 0.dp,
                                        valueFormatter = axisValueFormatter,
                                        labelRotationDegrees = -90f,
                                    ),
                                    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.HistoryPoint.Vibration -> {
                            Column {

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

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

                            }
                        }
                        null -> {}
                    }

                }

            }
            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
                    ), label = ""
                )

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

            }

        }

    }

}

@Composable
private fun Exception() {
    Box(
        modifier = Modifier
            .padding(8.dp)
            .fillMaxWidth()
            .aspectRatio(2f),
    ){

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

    }

}

class AccelerometerHistoryContract {

    sealed class Event : ViewEvent {

        object StopMeasure : Event()

        object OnExport : Event()

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

        data class OnRefreshHistory(
            val bleName: String,
            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 bleName: String,
            val loadingHistoryState : ProgressState<List<Ble.Accelerometer.HistoryPoint>>
        ) : State()

        object Exception : State()

    }

    sealed class Effect : ViewSideEffect {

    }

}



@HiltViewModel
class AccelerometerHistoryViewModel @Inject constructor(
    private val getAccelerometerHistoryBySerial: GetAccelerometerHistoryBySerial,
    private val exportToXlsx: ExportToXlsx,
    private val getBleBySerial: GetBleBySerial
) : BaseViewModel<AccelerometerHistoryContract.State, AccelerometerHistoryContract.Event, AccelerometerHistoryContract.Effect>() {

    var measureJob: Job? = null

    private var lastSerial: String? = null

    override fun setInitialState() = AccelerometerHistoryContract.State.Display(
        "",
        ProgressState.Indeterminate
    )

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

    private fun reduce(
        state: AccelerometerHistoryContract.State,
        event: AccelerometerHistoryContract.Event.OnExport
    ) {

        if(state is AccelerometerHistoryContract.State.Display){
            if(state.loadingHistoryState is ProgressState.Finished){
                exportToXlsx.invoke(state.bleName, state.loadingHistoryState.data)
            }
        }

    }

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

        measureJob?.cancel()
        measureJob = null

        setState {
            AccelerometerHistoryContract.State.Exception
        }

    }

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

        viewModelScope.launch {

            if(state is AccelerometerHistoryContract.State.Display) {

                if(lastSerial != event.serial) {

                    lastSerial = event.serial

                    setState {
                        AccelerometerHistoryContract.State.Display(event.bleName, ProgressState.Indeterminate)
                    }

                    measureJob?.cancel()
                    measureJob = null

                    measureJob = getAccelerometerHistoryBySerial(event.serial).onEach {
                        it.fold(
                            onSuccess = {
                                setState {
                                    AccelerometerHistoryContract.State.Display(event.bleName, it)
                                }
                            },
                            onFailure = {
                                setState {
                                    AccelerometerHistoryContract.State.Exception
                                }
                            }
                        )
                    }.launchIn(this)

                }

            }

        }

    }

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

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

            measureJob?.cancel()
            measureJob = null

            measureJob = getAccelerometerHistoryBySerial(event.serial).onEach {
                it.fold(
                    onSuccess = {
                        setState {
                            AccelerometerHistoryContract.State.Display(event.bleName, it)
                        }
                    },
                    onFailure = {
                        setState {
                            AccelerometerHistoryContract.State.Exception
                        }
                    }
                )
            }.launchIn(this)

        }
    }

}