/*
 * This file is part of BOINC.
 * http://boinc.berkeley.edu
 * Copyright (C) 2021 University of California
 *
 * BOINC is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 *
 * BOINC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
 */
package edu.berkeley.boinc

import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.core.content.edit
import androidx.preference.*
import edu.berkeley.boinc.rpc.GlobalPreferences
import edu.berkeley.boinc.rpc.HostInfo
import edu.berkeley.boinc.utils.Logging
import edu.berkeley.boinc.utils.setAppTheme
import java.io.File
import java.util.concurrent.ThreadLocalRandom
import kotlin.streams.asSequence


class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
    private val hostInfo = BOINCActivity.monitor!!.hostInfo // Get the hostinfo from client via RPC
    private val prefs = BOINCActivity.monitor!!.prefs
    private val charPool : List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
    private val passwordLength = 32
    private var authKey = ""


    override fun onResume() {
        super.onResume()
        preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
    }

    override fun onPause() {
        super.onPause()
        preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
    }

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
        if ("usedCpuCores" !in sharedPreferences) {
            sharedPreferences.edit { putInt("usedCpuCores", pctCpuCoresToNumber(hostInfo, prefs.maxNoOfCPUsPct)) }
        }
        if ("deviceName" !in sharedPreferences) {
            sharedPreferences.edit { putString("deviceName", hostInfo.domainName) }
        }

        if(authKey.isEmpty()) {
            authKey = readAuthFileContent()
            if (authKey.isEmpty()) {
                authKey = generateRandomString(passwordLength)
                writeAuthFileContent(authKey)
            }
            sharedPreferences.edit { putString("authenticationKey", authKey) }
        }

        val stationaryDeviceMode = BOINCActivity.monitor!!.stationaryDeviceMode
        val stationaryDeviceSuspected = BOINCActivity.monitor!!.isStationaryDeviceSuspected

        preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)

        setPreferencesFromResource(R.xml.root_preferences, rootKey)

        setAdvancedPreferencesVisibility()

        if (!stationaryDeviceSuspected) {
            findPreference<CheckBoxPreference>("stationaryDeviceMode")?.isVisible = false
        }
        if (stationaryDeviceMode) {
            findPreference<CheckBoxPreference>("suspendWhenScreenOn")?.isVisible = false
        }

        val usedCpuCores = findPreference<SeekBarPreference>("usedCpuCores")
        if (hostInfo.noOfCPUs <= 1) {
            usedCpuCores?.isVisible = false
        } else {
            usedCpuCores?.max = hostInfo.noOfCPUs
        }

        val preference = findPreference<EditTextPreference>("authenticationKey")!!
        preference.setSummaryProvider {
            getString(R.string.prefs_remote_boinc_relaunched) + '\n' +
            setAsterisks(authKey.length)
        }
    }

    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
        when (key) {
            // General
            "autostart" -> BOINCActivity.monitor!!.autostart = sharedPreferences.getBoolean(key, true)
            "showNotifications" -> BOINCActivity.monitor!!.showNotificationForNotices = sharedPreferences.getBoolean(key, true)
            "showAdvanced" -> {
                BOINCActivity.monitor!!.showAdvanced = sharedPreferences.getBoolean(key, false)
                setAdvancedPreferencesVisibility()
            }
            "suspendWhenScreenOn" -> BOINCActivity.monitor!!.suspendWhenScreenOn = sharedPreferences.getBoolean(key, true)
            "deviceName" -> BOINCActivity.monitor!!.setDomainName(sharedPreferences.getString(key, "") ?: "")
            "theme" -> setAppTheme(sharedPreferences.getString(key, "light")!!)

            // Network
            "networkWiFiOnly" -> {
                prefs.networkWiFiOnly = sharedPreferences.getBoolean(key, true)
                writeClientPrefs(prefs)
            }
            "dailyTransferLimitMB" -> {
                val dailyTransferLimitMB = sharedPreferences.getString(key, prefs.dailyTransferLimitMB.toString())
                prefs.dailyTransferLimitMB = dailyTransferLimitMB?.toDouble() ?: 0.0
                writeClientPrefs(prefs)
            }
            "dailyTransferPeriodDays" -> {
                val dailyTransferPeriodDays = sharedPreferences.getString(key, prefs.dailyTransferPeriodDays.toString())
                prefs.dailyTransferPeriodDays = dailyTransferPeriodDays?.toInt() ?: 0
                writeClientPrefs(prefs)
            }

            // Power
            "powerSources" -> {
                val powerSources = sharedPreferences.getStringSet(key,
                        resources.getStringArray(R.array.power_source_default).toSet()) ?: emptySet()
                Log.d(Logging.TAG, "powerSources: $powerSources")
                BOINCActivity.monitor!!.powerSourceAc = "wall" in powerSources
                BOINCActivity.monitor!!.powerSourceUsb = "usb" in powerSources
                BOINCActivity.monitor!!.powerSourceWireless = "wireless" in powerSources
                prefs.runOnBatteryPower = "battery" in powerSources
                writeClientPrefs(prefs)
            }
            "stationaryDeviceMode" -> BOINCActivity.monitor!!.stationaryDeviceMode = sharedPreferences.getBoolean(key, false)
            "maxBatteryTemp" -> {
                prefs.batteryMaxTemperature = sharedPreferences.getString(key, "40")?.toDouble() ?: 40.0
                writeClientPrefs(prefs)
            }
            "minBatteryLevel" -> {
                prefs.batteryChargeMinPct = sharedPreferences.getInt(key, 90).toDouble()
                writeClientPrefs(prefs)
            }

            // CPU
            "usedCpuCores" -> {
                val usedCpuCores = sharedPreferences.getInt(key, pctCpuCoresToNumber(hostInfo,
                        prefs.maxNoOfCPUsPct))
                prefs.maxNoOfCPUsPct = numberCpuCoresToPct(hostInfo, usedCpuCores)
                writeClientPrefs(prefs)
            }
            "cpuUsageLimit" -> {
                prefs.cpuUsageLimit = sharedPreferences.getInt(key, 100).toDouble()
                writeClientPrefs(prefs)
            }
            "suspendCpuUsage" -> {
                prefs.suspendCpuUsage = sharedPreferences.getInt(key, 50).toDouble()
                writeClientPrefs(prefs)
            }

            // Storage
            "diskMaxUsedPct" -> {
                prefs.diskMaxUsedPct = sharedPreferences.getInt(key, 90).toDouble()
                writeClientPrefs(prefs)
            }
            "diskMinFreeGB" -> {
                prefs.diskMinFreeGB = sharedPreferences.getString(key, "0.1")?.toDouble() ?: 0.1
                writeClientPrefs(prefs)
            }
            "diskInterval" -> {
                prefs.diskInterval = sharedPreferences.getString(key, "60")?.toDouble() ?: 60.0
                writeClientPrefs(prefs)
            }

            // Memory
            "maxRamUsedIdle" -> {
                prefs.ramMaxUsedIdleFrac = sharedPreferences.getInt(key, 50).toDouble()
                writeClientPrefs(prefs)
            }

            // Other
            "workBufMinDays" -> {
                prefs.workBufMinDays = sharedPreferences.getString(key, "0.1")?.toDouble() ?: 0.1
                writeClientPrefs(prefs)
            }
            "workBufAdditionalDays" -> {
                prefs.workBufAdditionalDays = sharedPreferences.getString(key, "0.5")?.toDouble() ?: 0.5
                writeClientPrefs(prefs)
            }

            // Remote
            "authenticationKey" -> {
                val currentAuthKey = sharedPreferences.getString(key, "")!!
                if (currentAuthKey.isEmpty()) {
                    sharedPreferences.edit { putString(key, authKey) }
                    findPreference<EditTextPreference>(key)?.text = authKey
                    Toast.makeText(activity, R.string.prefs_remote_empty_password, Toast.LENGTH_SHORT).show()
                } else {
                    authKey = currentAuthKey
                    writeAuthFileContent(authKey)
                    quitClient()
                }
            }

            "remoteEnable" -> {
                val isRemote = sharedPreferences.getBoolean(key, false)
                BOINCActivity.monitor!!.isRemote = isRemote
                findPreference<EditTextPreference>("authenticationKey")?.isVisible = isRemote
                quitClient()
            }

            // Debug
            "clientLogFlags" -> {
                val flags = sharedPreferences.getStringSet(key, emptySet()) ?: emptySet()
                BOINCActivity.monitor!!.setCcConfigAsync(formatOptionsToCcConfig(flags))
            }
            "logLevel" -> {
                val logLevel = sharedPreferences.getInt(key,
                    resources.getInteger(R.integer.prefs_default_loglevel))
                BOINCActivity.monitor!!.logLevel = logLevel
                Logging.setLogLevel(logLevel)
            }
        }
    }

    private fun pctCpuCoresToNumber(hostInfo: HostInfo, pct: Double) =
            1.0.coerceAtLeast(hostInfo.noOfCPUs.toDouble() * (pct / 100.0)).toInt()

    private fun numberCpuCoresToPct(hostInfo: HostInfo, ncpus: Int) = ncpus / hostInfo.noOfCPUs.toDouble() * 100

    private fun formatOptionsToCcConfig(options: Set<String>): String {
        val builder = StringBuilder()
        builder.append("<cc_config>\n <log_flags>\n")
        for (option in options) {
            builder.append("  <").append(option).append("/>\n")
        }
        builder.append(" </log_flags>\n <options>\n </options>\n</cc_config>")
        return builder.toString()
    }

    private fun setAdvancedPreferencesVisibility() {
        val showAdvanced = BOINCActivity.monitor!!.showAdvanced

        findPreference<EditTextPreference>("dailyTransferLimitMB")?.isVisible = showAdvanced
        findPreference<EditTextPreference>("dailyTransferPeriodDays")?.isVisible = showAdvanced

        findPreference<PreferenceCategory>("cpu")?.isVisible = showAdvanced
        findPreference<PreferenceCategory>("storage")?.isVisible = showAdvanced
        findPreference<PreferenceCategory>("memory")?.isVisible = showAdvanced
        findPreference<PreferenceCategory>("other")?.isVisible = showAdvanced
        findPreference<PreferenceCategory>("debug")?.isVisible = showAdvanced
        findPreference<PreferenceCategory>("remote")?.isVisible = showAdvanced
        val isRemote = findPreference<CheckBoxPreference>("remoteEnable")?.isChecked
        findPreference<EditTextPreference>("authenticationKey")?.isVisible = showAdvanced && isRemote == true
    }

    private fun writeClientPrefs(prefs: GlobalPreferences) {
        BOINCActivity.monitor!!.setGlobalPreferencesAsync(prefs) {
            success: Boolean ->
                Log.d(Logging.TAG, "writeClientPrefs() async call returned: $success")
        }
    }

    private fun generateRandomString(length: Int) : String {
        return ThreadLocalRandom.current()
                .ints(length.toLong(), 0, charPool.size)
                .asSequence()
                .map(charPool::get)
                .joinToString("")
    }

    // Return the password in asterisks
    private fun setAsterisks(length: Int): String {
        return "*".repeat(length)
    }

    private fun readAuthFileContent(): String {
        val authFile = File(BOINCActivity.monitor!!.authFilePath)
        return if (authFile.exists()) authFile.bufferedReader().readLine() else ""
    }

    private fun writeAuthFileContent(value: String) {
        File(BOINCActivity.monitor!!.authFilePath).writeText(value)
    }

    private fun quitClient() {
        BOINCActivity.monitor!!.quitClientAsync { result: Boolean ->
            Log.d(Logging.TAG, "SettingActivity: quitClient returned: $result")
        }
    }
}
