package com.laseroptek.raman.repository

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.laseroptek.raman.const.PresetList
import com.laseroptek.raman.const.SprayDcdList
import com.laseroptek.raman.const.VoltageTable
import com.laseroptek.raman.data.model.LifeTime
import com.laseroptek.raman.data.model.Preset
import com.laseroptek.raman.data.model.Refer2
import com.laseroptek.raman.data.model.VoltageEntry
import com.laseroptek.raman.data.model.serial.EnergyMeasured
import com.laseroptek.raman.data.model.serial.QSwitch
import com.laseroptek.raman.data.model.serial.SprayDcd
import com.laseroptek.raman.data.model.serial.Temperature
import com.laseroptek.raman.data.source.preference.Preference
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import timber.log.Timber

const val DataStore_NAME = "ramanDataStore"

val Context.datastore : DataStore<Preferences> by preferencesDataStore(name = DataStore_NAME)

@ViewModelScoped
class PreferenceRepository(private val context: Context) : Preference {
    private val gson = Gson()

    companion object{
        // Native Type
        val SLIDER_VOLUME = floatPreferencesKey("SLIDER_VOLUME")
        val GUIDE_BEAM = floatPreferencesKey("GUIDE_BEAM")
        val OP_TIME_HOUR = longPreferencesKey("OP_TIME_HOUR")
        val LAMP_COUNT = intPreferencesKey("LAMP_COUNT")
        val GUIDE_BEAM_MIN = intPreferencesKey("GUIDE_BEAM_MIN")
        val GUIDE_BEAM_MAX = intPreferencesKey("GUIDE_BEAM_MAX")
        val DCD_COUNT = intPreferencesKey("DCD_COUNT")
        val HP_COUNT = intPreferencesKey("HP_COUNT")
        val SPRAY_DCD_INDEX = intPreferencesKey("SPRAY_DCD_INDEX")

        // List of Custom Object Type
        val VOLTAGE_TABLE = stringPreferencesKey("VOLTAGE_TABLE")
        val PRESET_LIST = stringPreferencesKey("PRESET_LIST")
        val SPRAY_DCD_LIST = stringPreferencesKey("SPRAY_DCD_LIST")

        // Custom Object Type
        val SPRAY_DCD = stringPreferencesKey("SPRAY_DCD")
        val Q_SWITCH = stringPreferencesKey("Q_SWITCH")
        val LIFE_TIME = stringPreferencesKey("LIFE_TIME")
        val TEMPERATURE = stringPreferencesKey("TEMPERATURE")
        val TEMPERATURE_WRITE = stringPreferencesKey("TEMPERATURE_WRITE")
        val ENERGY_MEASURED_WRITE = stringPreferencesKey("ENERGY_MEASURED_WRITE")
        val ENERGY_REFER_2 = stringPreferencesKey("ENERGY_REFER_2")

        // List of String Object Type
        val PRODUCT_SERIAL = stringPreferencesKey("PRODUCT_SERIAL")
        val LASER_HAND_SERIAL = stringPreferencesKey("LASER_HAND_SERIAL")
        val POWER_SUPPLY_SERIAL = stringPreferencesKey("POWER_SUPPLY_SERIAL")
    }

    override suspend fun clearAllPreferences() {
        context.datastore.edit { preferences ->
            preferences.clear()
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Map of Native Type

    override suspend fun saveSliderVolumeToPreference(volume: Float) {
        try {
            context.datastore.edit { preferences ->
                preferences[SLIDER_VOLUME] = volume
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize SLIDER_VOLUME to JSON.")
        }
    }
    override suspend fun getSliderVolumeFromPreference(): Flow<Float> {
        return context.datastore.data.map { preferences ->
            preferences[SLIDER_VOLUME] ?: 0f
        }.distinctUntilChanged().catch { e ->
            Timber.e(e, "Failed to get or parse SLIDER_VOLUME. Emitting default.")
            emit(0f)
        }
    }

    override suspend fun saveGuideBeamToPreference(guideBeam: Float) {
        try {
            context.datastore.edit { preferences ->
                preferences[GUIDE_BEAM] = guideBeam
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize GUIDE_BEAM to JSON.")
        }
    }
    override suspend fun getGuideBeamFromPreference(): Flow<Float> {
        return context.datastore.data.map { preferences ->
            preferences[GUIDE_BEAM] ?: 0f
        }.distinctUntilChanged().catch { e ->
            Timber.e(e, "Failed to get or parse GUIDE_BEAM. Emitting default.")
            emit(0f)
        }
    }

    override suspend fun saveOpTimeHourToPreference(opTimeHour: Long) {
        try {
            context.datastore.edit { preferences ->
                preferences[OP_TIME_HOUR] = opTimeHour
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize OP_TIME_HOUR to JSON.")
        }
    }
    override suspend fun getOpTimeHourFromPreference(): Flow<Long> {
        return context.datastore.data.map { preferences ->
            preferences[OP_TIME_HOUR] ?: 0L
        }.distinctUntilChanged().catch { e ->
            Timber.e(e, "Failed to get or parse OP_TIME_HOUR. Emitting default.")
            emit(0L)
        }
    }

    override suspend fun saveHpCountToPreference(hpCount: Int) {
        try {
            context.datastore.edit { preferences ->
                preferences[HP_COUNT] = hpCount
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize HP_COUNT to JSON.")
        }
    }
    override suspend fun getHpCountToPreference(): Flow<Int> {
        return context.datastore.data.map { preferences ->
            preferences[HP_COUNT] ?: 0
        }.distinctUntilChanged().catch { e ->
            Timber.e(e, "Failed to get or parse HP_COUNT. Emitting default.")
            emit(0)
        }
    }

    override suspend fun saveLampCountToPreference(lampCount: Int) {
        try {
            context.datastore.edit { preferences ->
                preferences[LAMP_COUNT] = lampCount
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize LAMP_COUNT to JSON.")
        }
    }
    override suspend fun getLampCountFromPreference(): Flow<Int> {
        return context.datastore.data.map { preferences ->
            preferences[LAMP_COUNT] ?: 0
        }.distinctUntilChanged().catch { e ->
            Timber.e(e, "Failed to get or parse LAMP_COUNT. Emitting default.")
            emit(0)
        }
    }

    override suspend fun saveGuideBeamMinToPreference(guideBeamMin: Int) {
        try {
            context.datastore.edit { preferences ->
                preferences[GUIDE_BEAM_MIN] = guideBeamMin
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize GUIDE_BEAM_MIN to JSON.")
        }
    }
    override suspend fun getGuideBeamMinFromPreference(): Flow<Int> {
        return context.datastore.data.map { preferences ->
            preferences[GUIDE_BEAM_MIN] ?: 0
        }.distinctUntilChanged().catch { e ->
            Timber.e(e, "Failed to get or parse GUIDE_BEAM_MIN. Emitting default.")
            emit(0)
        }
    }

    override suspend fun saveGuideBeamMaxToPreference(guideBeamMax: Int) {
        try {
            context.datastore.edit { preferences ->
                preferences[GUIDE_BEAM_MAX] = guideBeamMax
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize GUIDE_BEAM_MAX to JSON.")
        }

    }
    override suspend fun getGuideBeamMaxFromPreference(): Flow<Int> {
        return context.datastore.data.map { preferences ->
            preferences[GUIDE_BEAM_MAX] ?: 0
        }.distinctUntilChanged().catch { e ->
            Timber.e(e, "Failed to get or parse GUIDE_BEAM_MAX. Emitting default.")
            emit(0)
        }
    }

    override suspend fun saveSprayDcdIndexToPreference(sprayDcdIndex: Int) {
        try {
            context.datastore.edit { preferences ->
                preferences[SPRAY_DCD_INDEX] = sprayDcdIndex
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize SPRAY_SPRAY_DCD_INDEX to JSON.")
        }
    }

    override suspend fun getSprayDcdIndexFromPreference(): Flow<Int> {
        return context.datastore.data.map { preferences ->
            preferences[SPRAY_DCD_INDEX] ?: 0
        }.distinctUntilChanged().catch { e ->
            Timber.e(e, "Failed to get or parse SPRAY_SPRAY_DCD_INDEX. Emitting default.")
            emit(0)
        }
    }

    override suspend fun saveDcdCountToPreference(dcdCount: Int) {
        try {
            context.datastore.edit { preferences ->
                preferences[DCD_COUNT] = dcdCount
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize DCD_COUNT to JSON.")
        }
    }
    override suspend fun getDcdCountFromPreference(): Flow<Int> {
        return context.datastore.data.map { preferences ->
            preferences[DCD_COUNT] ?: 0
        }.distinctUntilChanged().catch { e ->
            Timber.e(e, "Failed to get or parse DCD_COUNT. Emitting default.")
            emit(0)
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Map of Custom Object

    override suspend fun saveSprayDcdToPreference(sprayDcd: SprayDcd) {
        try {
            val jsonString = gson.toJson(sprayDcd)
            context.datastore.edit { preferences ->
                preferences[SPRAY_DCD] = jsonString
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize SPRAY_DCD to JSON.")
        }
    }
    override suspend fun getSprayDcdFromPreference(): Flow<SprayDcd> {
        return context.datastore.data.map { preferences ->
            preferences[SPRAY_DCD]
        }.distinctUntilChanged().map{ jsonString ->
            gson.fromJson(jsonString, SprayDcd::class.java) ?: SprayDcd()
        }.catch { e ->
            Timber.e(e, "Failed to get or parse SPRAY_DCD. Emitting default.")
            emit(SprayDcd())
        }
    }

    override suspend fun saveQSwitchToPreference(qSwitch: QSwitch) {
        try {
            val jsonString = gson.toJson(qSwitch)
            context.datastore.edit { preferences ->
                preferences[Q_SWITCH] = jsonString
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize Q_SWITCH to JSON.")
        }
    }
    override suspend fun getQSwitchFromPreference(): Flow<QSwitch> {
        return context.datastore.data.map { preferences ->
            preferences[Q_SWITCH]
        }.distinctUntilChanged().map{ jsonString ->
            gson.fromJson(jsonString, QSwitch::class.java) ?: QSwitch()
        }.catch { e ->
            Timber.e(e, "Failed to get or parse Q_SWITCH. Emitting default.")
            emit(QSwitch())
        }
    }

    override suspend fun saveLifeTimeToPreference(lifeTime: LifeTime) {
        try {
            val jsonString = gson.toJson(lifeTime)
            context.datastore.edit { preferences ->
                preferences[LIFE_TIME] = jsonString
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize LIFE_TIME to JSON.")
        }
    }
    override suspend fun getLifeTimeFromPreference(): Flow<LifeTime> {
        return context.datastore.data.map { preferences ->
            preferences[LIFE_TIME]
        }.distinctUntilChanged().map{ jsonString ->
            gson.fromJson(jsonString, LifeTime::class.java) ?: LifeTime()
        }.catch { e ->
            Timber.e(e, "Failed to get or parse LIFE_TIME. Emitting default.")
            emit(LifeTime())
        }
    }

    override suspend fun saveTemperatureToPreference(temp: Temperature) {
        try {
            val jsonString = gson.toJson(temp)
            context.datastore.edit { preferences ->
                preferences[TEMPERATURE] = jsonString
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize TEMPERATURE to JSON.")
        }
    }
    override suspend fun getTemperatureFromPreference(): Flow<Temperature> {
        return context.datastore.data.map { preferences ->
            preferences[TEMPERATURE]
        }.distinctUntilChanged().map{ jsonString ->
            gson.fromJson(jsonString, Temperature::class.java) ?: Temperature()
        }.catch { e ->
            Timber.e(e, "Failed to get or parse TEMPERATURE. Emitting default.")
            emit(Temperature())
        }
    }

    override suspend fun saveTemperatureWriteToPreference(temp: Temperature) {
        try {
            val jsonString = gson.toJson(temp)
            context.datastore.edit { preferences ->
                preferences[TEMPERATURE_WRITE] = jsonString
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize TEMPERATURE_WRITE to JSON.")
        }
    }
    override suspend fun getTemperatureWriteFromPreference(): Flow<Temperature> {
        return context.datastore.data.map { preferences ->
            preferences[TEMPERATURE_WRITE]
        }.distinctUntilChanged().map{ jsonString ->
            gson.fromJson(jsonString, Temperature::class.java) ?: Temperature()
        }.catch { e ->
            Timber.e(e, "Failed to get or parse TEMPERATURE_WRITE. Emitting default.")
            emit(Temperature())
        }
    }

    override suspend fun saveEnergyMeasuredWriteToPreference(temp: EnergyMeasured) {
        try {
            val jsonString = gson.toJson(temp)
            context.datastore.edit { preferences ->
                preferences[ENERGY_MEASURED_WRITE] = jsonString
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize ENERGY_MEASURED_WRITE to JSON.")
        }
    }
    override suspend fun getEnergyMeasuredWriteFromPreference(): Flow<EnergyMeasured> {
        return context.datastore.data.map { preferences ->
            preferences[ENERGY_MEASURED_WRITE]
        }.distinctUntilChanged().map{jsonString ->
            gson.fromJson(jsonString, EnergyMeasured::class.java) ?: EnergyMeasured()
        }.catch { e ->
            Timber.e(e, "Failed to get or parse ENERGY_MEASURED_WRITE. Emitting default.")
            emit(EnergyMeasured())
        }
    }

    override suspend fun saveEnergyRefer2ToPreference(refer2: Refer2) {
        try {
            val jsonString = gson.toJson(refer2)
            context.datastore.edit { preferences ->
                preferences[ENERGY_REFER_2] = jsonString
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize ENERGY_REFER_2 to JSON.")
        }
    }
    override suspend fun getEnergyRefer2FromPreference(): Flow<Refer2> {
        return context.datastore.data.map { preferences ->
            preferences[ENERGY_REFER_2]
        }.distinctUntilChanged().map{ jsonString ->
            gson.fromJson(jsonString, Refer2::class.java) ?: Refer2()
        }.catch { e ->
            Timber.e(e, "Failed to get or parse ENERGY_REFER_2. Emitting default.")
            emit(Refer2())
        }
    }


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Map of Pair<Float, Float> to Int to List<VoltageTable>

    override suspend fun saveVoltageTableToPreference(voltageTable: Map<Pair<Float, Float>, Int>) {
        try {
            val serializableList = voltageTable.map { (key, value) ->
                VoltageEntry(key.first, key.second, value)
            }
            context.datastore.edit { preferences ->
                preferences[VOLTAGE_TABLE] = gson.toJson(serializableList)
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize VOLTAGE_TABLE to JSON.")
        }
    }
    override suspend fun getVoltageTableFromPreference(): Flow<Map<Pair<Float, Float>, Int>> {
        return context.datastore.data.map { preferences ->
            preferences[VOLTAGE_TABLE]
        }.distinctUntilChanged().map{ jsonString ->
            if (!jsonString.isNullOrBlank()) {
                val type = object : TypeToken<List<VoltageEntry>>() {}.type
                val list: List<VoltageEntry> = gson.fromJson(jsonString, type) ?: emptyList()
                list.associate { Pair(it.pulse, it.energy) to it.voltage }
            } else {
                VoltageTable
            }
        }.catch { e ->
            Timber.e(e, "Failed to get or parse VOLTAGE_TABLE. Emitting default.")
            emit(VoltageTable)
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // List of Custom Object

    override suspend fun savePresetListToPreference(presetList: List<Preset>) {
        try {
            context.datastore.edit { preferences ->
                preferences[PRESET_LIST] = gson.toJson(presetList)
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize VOLTAGE_TABLE to JSON.")
        }
    }

    override suspend fun getPresetListFromPreference(): Flow<List<Preset>> {
        return context.datastore.data.map { preferences ->
            preferences[PRESET_LIST]
        }.distinctUntilChanged().map { jsonString ->
            if (!jsonString.isNullOrBlank()) {
                val type = object : TypeToken<List<Preset>>() {}.type
                gson.fromJson(jsonString, type) ?: PresetList
            } else {
                PresetList
            }
        }.catch { e ->
            Timber.e(e, "Failed to get or parse PRESET_LIST. Emitting default.")
            emit(PresetList)
        }
    }

    override suspend fun saveSprayDcdList(sprayDcdList: List<SprayDcd>) {
        try {
            context.datastore.edit { preferences ->
                preferences[SPRAY_DCD_LIST] = gson.toJson(sprayDcdList)
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize SPRAY_DCD_LIST to JSON.")
        }
    }

    override suspend fun getSprayDcdList(): Flow<List<SprayDcd>> {
        return context.datastore.data.map { preferences ->
            preferences[SPRAY_DCD_LIST]
        }.distinctUntilChanged().map { jsonString ->
            if (!jsonString.isNullOrBlank()) {
                val type = object : TypeToken<List<SprayDcd>>() {}.type
                gson.fromJson(jsonString, type) ?: SprayDcdList
            } else {
                SprayDcdList
            }
        }.catch { e ->
            Timber.e(e, "Failed to get or parse SPRAY_DCD_LIST. Emitting default.")
            emit(SprayDcdList)
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // List of Native (String)

    override suspend fun saveProductSerialListToPreference(productList: List<String>) {
        try {
            context.datastore.edit { preferences ->
                preferences[PRODUCT_SERIAL] = gson.toJson(productList)
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize PRODUCT_SERIAL to JSON.")
        }
    }

    override suspend fun getProductSerialListFromPreference(): Flow<List<String>> {
        return context.datastore.data.map { preferences ->
            preferences[PRODUCT_SERIAL]
        }.distinctUntilChanged().map{ jsonString ->
            if (!jsonString.isNullOrBlank()) {
                val type = object : TypeToken<List<String>>() {}.type
                gson.fromJson(jsonString, type) ?: listOf("B", "U", "O", "C", "L", "D")
            } else {
                listOf("B", "U", "O", "C", "L", "D")
            }
        }.catch { e ->
            Timber.e(e, "Failed to get or parse PRODUCT_SERIAL. Emitting default.")
            emit(listOf("B", "U", "O", "C", "L", "D"))
        }
    }

    override suspend fun saveLaserHandSerialListToPreference(serialList: List<String>) {
        try {
            context.datastore.edit { preferences ->
                preferences[LASER_HAND_SERIAL] = gson.toJson(serialList)
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize LASER_HAND_SERIAL to JSON.")
        }
    }
    override suspend fun getLaserHandSerialListFromPreference(): Flow<List<String>> {
        return context.datastore.data.map { preferences ->
            preferences[LASER_HAND_SERIAL]
        }.distinctUntilChanged().map { jsonString ->
            if (!jsonString.isNullOrBlank()) {
                val type = object : TypeToken<List<String>>() {}.type
                gson.fromJson(jsonString, type) ?: listOf("B", "U", "O", "C", "L", "D")
            } else {
                listOf("B", "U", "O", "C", "L", "D")
            }
        }.catch { e ->
            Timber.e(e, "Failed to get or parse LASER_HAND_SERIAL. Emitting default.")
            emit(listOf("B", "U", "O", "C", "L", "D"))
        }
    }

    override suspend fun savePowerSupplySerialListToPreference(powerSupplySerialList: List<String>) {
        try {
            context.datastore.edit { preferences ->
                preferences[POWER_SUPPLY_SERIAL] = gson.toJson(powerSupplySerialList)
            }
        } catch (e: Exception) {
            Timber.e(e, "Failed to serialize POWER_SUPPLY_SERIAL to JSON.")
        }
    }
    override suspend fun getPowerSupplySerialListFromPreference(): Flow<List<String>> {
        return context.datastore.data.map { preferences ->
            preferences[POWER_SUPPLY_SERIAL]
        }.distinctUntilChanged().map { jsonString ->
            if (!jsonString.isNullOrBlank()) {
                val type = object : TypeToken<List<String>>() {}.type
                gson.fromJson(jsonString, type) ?: listOf("B", "U", "O", "C", "L", "D")
            } else {
                listOf("B", "U", "O", "C", "L", "D")
            }
        }.catch { e ->
            Timber.e(e, "Failed to get or parse POWER_SUPPLY_SERIAL. Emitting default.")
            emit(listOf("B", "U", "O", "C", "L", "D"))
        }
    }
}