# TimeoutActivityHelper.kt

    /*
     * The MIT License
     *
     * Original work sponsored and donated by The Danish Health Data Authority (http://www.sundhedsdatastyrelsen.dk)
     *
     * Copyright (C) 2021 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.util

    import android.app.Activity
    import android.content.Intent
    import android.os.Bundle
    import androidx.lifecycle.LifecycleCoroutineScope
    import com.commonsense.android.kotlin.base.extensions.use
    import com.commonsense.android.kotlin.base.extensions.weakReference
    import dk.nsp.mingraviditet.ui.useradmission.UserAdmissionActivity
    import dk.nsp.shared.util.launchDefault
    import kotlinx.coroutines.Job
    import kotlinx.coroutines.delay
    import timber.log.Timber
    import java.time.LocalDateTime
    import java.time.temporal.ChronoUnit

    class TimeoutActivityHelper(
        private val userStateHandler: UserStateHandler,
        private val lifecycleScope: LifecycleCoroutineScope,
        activity: Activity
    ) {
        private val activity = activity.weakReference()

        //the bundle we are using as a "semi" persisted storage. this will survive empty process.
        private val ourBundle: Bundle = Bundle()

        //bundle backed string that is a representation of the last interaction date time.
        private var lastInteractionTimeString: String? by BundleStringDelegate { ourBundle }

        // the timed job that inspects our timeout state at regular intervals.
        private var timerJob: Job? = null

        /**
         * should be called from activity
         * @param savedInstanceState Bundle?
         */
        fun onCreate(savedInstanceState: Bundle?) {
            //restore bundle
            val ourBundleRestored =
                savedInstanceState?.getBundle(bundleName)

            //if we have bundle, then restore ourBundle.
            if (ourBundleRestored != null) {
                ourBundle.putAll(ourBundleRestored)
                //recreate (thus we are not to initialize the userInteraction)
            } else {
                //Force app to register an interaction to start the timer
                onUserInteraction()
            }
            //make sure the timerjob is running no matter what.
            recreateTimerJob()
        }

        /**
         * should be called from activity
         */
        fun onUserInteraction() {
            resetLastInteractionTimer()
        }

        //reset the last interaction time
        private fun resetLastInteractionTimer() {
            lastInteractionTimeString = LocalDateTime.now().toString()
        }

        /**
         * should be called from activity
         * @param outState Bundle
         */
        fun onSaveInstanceState(outState: Bundle) {
            outState.putBundle(bundleName, ourBundle)
        }

        //should only be called once, but handles iff some lifecycle will recreate it.
        private fun recreateTimerJob() {
            Timber.d("recreateTimerJob")
            timerJob?.cancel()
            timerJob = lifecycleScope.launchDefault {
                while (true) {
                    //this is the "drift" time, the time we can get past the timeout.
                    delay(testTimeInMs)
                    testTime()
                }
            }
        }

        /**
         * should be called from activity
         */
        fun onResume() {
            testTime()
        }

        /**
         * Tests if we have timed out.
         */
        private fun testTime() {
            val timeSinceLastInteraction = ChronoUnit.SECONDS.between(
                LocalDateTime.parse(lastInteractionTimeString),
                LocalDateTime.now()
            )

            //skip if we are not timed out
            if (timeoutInSeconds > timeSinceLastInteraction) {
                return
            }

            //we are timed out, when visible / on Resumed then verify we are logged in
            // (so iff it happens twice in a row, then we would only clear the sessions and go to start once)
            lifecycleScope.launchWhenResumed {
                activity.use {
                    userStateHandler.logoutAndClearData()
                    startActivity(Intent(this, UserAdmissionActivity::class.java))
                    finishAffinity()
                }
                timerJob?.cancel()
            }
        }

        fun onDestroy() {
            timerJob?.cancel()
        }

        companion object {
            private const val defaultTimeoutInSeconds = 300
            var timeoutInSeconds = defaultTimeoutInSeconds

            const val bundleName = "MinGraviditetTimeoutActivityHelperBundle"

            // This is the interval at which we check if we should log out
            val testTimeInMs = 5000L
        }
    }