# DataCardRepository.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.domain.repository

    import android.content.Context
    import androidx.lifecycle.LiveData
    import androidx.lifecycle.MediatorLiveData
    import androidx.lifecycle.MutableLiveData
    import dk.nsp.api.getBackendApiLayer
    import dk.nsp.models.CprIdentifiedPerson
    import dk.nsp.models.DataCard
    import dk.nsp.models.DataCardWarningState
    import dk.nsp.models.DateOfBirth
    import dk.nsp.shared.*
    import dk.nsp.shared.util.asyncDefault
    import dk.nsp.shared.util.sharedprefs.SharedPrefsHelper
    import dk.nsp.util.getMostRecentDataCard
    import dk.nsp.util.getPreselectionOrMostRecent
    import dk.nsp.util.onlyHasPreviousCourses
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.Deferred
    import timber.log.Timber
    import java.time.LocalDateTime
    import java.util.*

    class DataCardRepository(context: Context) : ClearableRepository {
        private val dataCardController = context.getBackendApiLayer().maternityDataCardController
        private val configurationController = context.getBackendApiLayer().configurationController
        private val sharedPrefsHelper = SharedPrefsHelper(context)

        private val _dataCards: MutableLiveData<List<DataCard>> = MutableLiveData()

        private val _dataCardWarning = MutableLiveData<DataCardWarningState?>()
        val dataCardWarning: LiveData<DataCardWarningState?> = _dataCardWarning

        var userInfoFromDataCard: CprIdentifiedPerson? = null
            private set

        private val _selectedDataCard: MutableLiveData<DataCard?> = MutableLiveData()
        val currentDataCard: LiveData<DataCard?> =
            MediatorLiveData<DataCard?>().apply {
                addSource(_selectedDataCard) {
                    if (it != null) {
                        _dataCardWarning.postValue(null)
                    }
                    postValue(it)
                }
            }

        private val _dateOfBirthForCurrentDataCard: MutableLiveData<DateOfBirth?> =
            MutableLiveData(null)
        val dateOfBirthForCurrentDataCard: LiveData<LocalDateTime?> =
            MediatorLiveData<LocalDateTime?>().apply {
                addSource(_dateOfBirthForCurrentDataCard) {
                    postValue(it?.dateOfBirth)
                }
            }

        fun setDataCard(scope: CoroutineScope, dataCard: DataCard?) = scope.asyncDefault {
            sharedPrefsHelper.lastSelectedDataCardId = dataCard?.episodeOfCareIdentifier
            _selectedDataCard.postValue(dataCard)

            //If datacard is not null, try to get it's dateOfBirth
            dataCard?.episodeOfCareIdentifier?.let { episodeOfCareId ->
                getDateOfBirthForCurrentSelection(this, episodeOfCareId).await()
            }
        }

        fun getDataCards(
            scope: CoroutineScope
        ): Deferred<ResultWrapper<List<DataCard>>> =
            scope.asyncDefault {
                _dataCards.value?.let { return@asyncDefault Success(it) }

                val dataCards = dataCardController.getMaternityDataCard(this).await()

                when (dataCards) {
                    is Failed -> Timber.e(dataCards.error, "No datacards gotten")
                    is Success -> {
                        userInfoFromDataCard = dataCards.value.getMostRecentDataCard()?.recordTarget
                        val filteredDataCards = dataCards.value.filter {
                            when (it.documentStatus) {
                                DataCard.DocumentStatus.ACTIVE,
                                DataCard.DocumentStatus.CLOSED -> true
                                DataCard.DocumentStatus.NEW,
                                DataCard.DocumentStatus.PARTIAL -> false
                                null -> false
                            }
                        }

                        _dataCards.postValue(filteredDataCards)

                        //Handle warning states
                        when {
                            filteredDataCards.isEmpty() -> _dataCardWarning.postValue(
                                DataCardWarningState.NoCourses
                            )
                            filteredDataCards.onlyHasPreviousCourses() -> _dataCardWarning.postValue(
                                DataCardWarningState.NoActiveCourse
                            )
                        }

                        @Suppress("DeferredResultUnused")
                        setDataCard(
                            this,
                            filteredDataCards.getPreselectionOrMostRecent(
                                sharedPrefsHelper.lastSelectedDataCardId
                            )
                        )
                    }
                }

                dataCards
            }

        private fun getDateOfBirthForCurrentSelection(
            scope: CoroutineScope,
            episodeOfCareId: String?
        ): Deferred<ResultWrapper<DateOfBirth>> =
            scope.asyncDefault {
                val episodeOfCareIdentifier = episodeOfCareId ?: return@asyncDefault Failed(
                    IllegalStateException("episodeOfCareIdentifier was null while trying to update date of birth")
                )

                configurationController.getDateOfBirth(scope, episodeOfCareIdentifier).await()
                    .mapSuccess {
                        _dateOfBirthForCurrentDataCard.postValue(it)
                        it
                    }
            }

        fun updateDateOfBirthForCurrentSelection(
            scope: CoroutineScope,
            newDate: LocalDateTime?
        ): Deferred<ResultWrapper<DateOfBirth>> = scope.asyncDefault {
            val episodeOfCareIdentifier =
                currentDataCard.value?.episodeOfCareIdentifier ?: return@asyncDefault Failed(
                    IllegalStateException("episodeOfCareIdentifier was null while trying to update date of birth")
                )

            val updateDateOfBirthRes =
                configurationController.updateDateOfBirth(scope, episodeOfCareIdentifier, newDate)
                    .await()

            Timber.d("updateDateOfBirthRes $updateDateOfBirthRes")
            updateDateOfBirthRes.mapSuccess {
                _dateOfBirthForCurrentDataCard.postValue(it)
                it
            }
        }

        override fun clear() {
            _dateOfBirthForCurrentDataCard.value = null
            _dataCards.value = null
            _selectedDataCard.value = null
        }

    }