# AppointmentsViewModel.kt

    /*
     * The MIT License
     *
     * Original work sponsored and donated by The Danish Health Data Authority (http://www.sundhedsdatastyrelsen.dk)
     *
     * Copyright (C) 2020 The Danish Health Data Authority (http://www.sundhedsdatastyrelsen.dk)
     *
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy of
     * this software and associated documentation files (the "Software"), to deal in
     * the Software without restriction, including without limitation the rights to
     * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
     * of the Software, and to permit persons to whom the Software is furnished to do
     * so, subject to the following conditions:
     *
     *
     * The above copyright notice and this permission notice shall be included in all
     * copies or substantial portions of the Software.
     *
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     * SOFTWARE.
     */

    package dk.nsp.mingraviditet.ui.main.appointments

    import androidx.lifecycle.*
    import androidx.navigation.NavDirections
    import com.commonsense.android.kotlin.base.extensions.collections.categorizeInto
    import dagger.hilt.android.lifecycle.HiltViewModel
    import dk.nsp.domain.careplan.CareplanActivities
    import dk.nsp.domain.repository.CareplanRepository
    import dk.nsp.domain.repository.DataCardRepository
    import dk.nsp.mingraviditet.R
    import dk.nsp.mingraviditet.extensions.dueDate
    import dk.nsp.mingraviditet.ui.adapterviews.EmptyStateFullHeight
    import dk.nsp.mingraviditet.ui.adapterviews.IdentityIllustrationRow
    import dk.nsp.mingraviditet.ui.adapterviews.LoadingStateFullHeight
    import dk.nsp.mingraviditet.ui.adapterviews.models.SectionType
    import dk.nsp.mingraviditet.ui.main.appointments.details.ActivityNavData
    import dk.nsp.mingraviditet.ui.main.appointments.extensions.isFinished
    import dk.nsp.mingraviditet.ui.main.appointments.extensions.isPlannedOrExpected
    import dk.nsp.mingraviditet.ui.main.appointments.rows.*
    import dk.nsp.mingraviditet.util.android.recycler.RecyclerAdapterView
    import dk.nsp.mingraviditet.util.extensions.isAfterNow
    import dk.nsp.mingraviditet.util.extensions.isBeforeNow
    import dk.nsp.mingraviditet.util.extensions.toSingleEvent
    import dk.nsp.mingraviditet.util.extensions.transform
    import dk.nsp.mingraviditet.util.livedata.SingleEvent
    import dk.nsp.models.Activity
    import dk.nsp.models.CarePlan
    import dk.nsp.models.DataCard
    import dk.nsp.shared.util.launchDefault
    import dk.nsp.util.TextModel
    import java.time.LocalDateTime
    import javax.inject.Inject

    @HiltViewModel
    class AppointmentsViewModel @Inject constructor(
        private val careplanRepository: CareplanRepository,
        private val dataCardRepository: DataCardRepository
    ) : ViewModel() {

        private val _careplansMapped: LiveData<CareplanActivities> =
            careplanRepository.selectedCareplan.transform {
                sortActivitiesIntoModel(it)
            }

        val rowsToShow: LiveData<List<RecyclerAdapterView<*>>> =
            MediatorLiveData<List<RecyclerAdapterView<*>>>().apply {
                addSource(_careplansMapped) {
                    postValue(createRows(it, dataCardRepository.currentDataCard.value?.dueDate))
                }
                addSource(dataCardRepository.currentDataCard) {
                    postValue(createRows(_careplansMapped.value, it?.dueDate))
                }
            }

        private val dataCardObserver = Observer<DataCard?> { dataCard: DataCard? ->
            viewModelScope.launchDefault {
                dataCard?.episodeOfCareIdentifier?.let { episodeOfCardId ->
                    careplanRepository.getCareplans(this, episodeOfCardId).await()
                }
            }
        }

        init {
            dataCardRepository.currentDataCard.observeForever(dataCardObserver)
        }

        override fun onCleared() {
            dataCardRepository.currentDataCard.removeObserver(dataCardObserver)
            super.onCleared()
        }

        private fun createRows(
            careplanActivities: CareplanActivities?,
            dueDate: LocalDateTime?
        ): List<RecyclerAdapterView<*>> {
            return if (careplanActivities == null) {
                return listOf(LoadingStateFullHeight())
            } else if (careplanActivities.finishedActivities.isEmpty() && careplanActivities.plannedActivities.isEmpty()) {
                createEmptyState()
            } else {
                mapActivitiesIntoRows(careplanActivities, dueDate)
            }
        }

        private val _navigateTo = MutableLiveData<SingleEvent<NavDirections>>()
        val navigateTo: LiveData<SingleEvent<NavDirections>> = _navigateTo

        private fun sortActivitiesIntoModel(careplan: CarePlan?): CareplanActivities {
            // Sort all activities into past and future:
            val activities = careplan?.activities ?: listOf()
            val (finished, planned) = activities.categorizeInto(
                { it.isFinished() },
                { it.isPlannedOrExpected() }
            )

            //Final product:
            return CareplanActivities(finished, planned)
        }

        private fun mapActivitiesIntoRows(
            activities: CareplanActivities?,
            dueDate: LocalDateTime?
        ): List<RecyclerAdapterView<*>> {

            val finished = setupSection(
                activities?.finishedActivities ?: listOf(),
                if (dueDate != null && dueDate.isBeforeNow()) dueDate else null,
                isFinishedSection = true
            )

            val planned = setupSection(
                activities?.plannedActivities ?: listOf(),
                if (dueDate != null && dueDate.isAfterNow()) dueDate else null,
                isFinishedSection = false
            )

            return finished + planned + listOf(IdentityIllustrationRow(SectionType.Activities(planned.isEmpty())))
        }

        private fun setupSection(
            activities: List<Activity>,
            dueDate: LocalDateTime?,
            isFinishedSection: Boolean
        ): List<RecyclerAdapterView<*>> {
            val result = mutableListOf<RecyclerAdapterView<*>>()

            if (activities.isNotEmpty()) {
                val headerRow = if (isFinishedSection) {
                    ActivityFinishedHeaderRow()
                } else {
                    ActivityHeaderRow()
                }
                result.add(headerRow)

                result.addAll(activities.map {
                    ActivityRow(ActivityRowBindingModel(it) {
                        triggerActivityNavigation(it)
                    })
                })

                if (dueDate != null) {
                    val indexForDueDate = result.indexOfFirst {
                        (it as? ActivityRow)?.model?.activity?.sortDate?.isAfter(dueDate) == true
                    }

                    if (indexForDueDate != -1) {
                        result.add(indexForDueDate, DueDateRow(dueDate, isFinishedSection))
                    } else {
                        result.add(DueDateRow(dueDate, isFinishedSection))
                    }
                }
            }

            return result
        }

        private fun triggerActivityNavigation(activity: Activity) {
            _navigateTo.postValue(
                AppointmentsFragmentDirections.actionKnowledgeFragmentToCareplanActivityDetailsFragment(
                    ActivityNavData.WithActivity(activity)
                ).toSingleEvent()
            )
        }

        private fun createEmptyState(): List<RecyclerAdapterView<*>> {
            return listOf(
                EmptyStateFullHeight(
                    TextModel.from(R.string.careplan_activities_empty_state_title),
                    TextModel.from(R.string.careplan_activities_empty_state_description)
                )
            )
        }
    }