package com.margelo.nitro.lunardatepicker.services import android.util.Log import android.util.LruCache import com.margelo.nitro.lunardatepicker.LDP_ConfigParams import com.margelo.nitro.lunardatepicker.LDP_CustomLanguage import com.margelo.nitro.lunardatepicker.LDP_CustomStyle import com.margelo.nitro.lunardatepicker.LDP_PresentParams import com.margelo.nitro.lunardatepicker.models.PickerConfig import java.security.MessageDigest import java.util.concurrent.ConcurrentHashMap /** * High-performance configuration caching service with multi-level caching * Provides O(1) configuration building, validation, and color parsing */ class ConfigurationCacheService { companion object { private const val TAG = "ConfigurationCache" private const val CONFIG_CACHE_SIZE = 50 // Cache 50 different configurations private const val VALIDATION_CACHE_SIZE = 100 // Cache 100 validation results private const val COLOR_CACHE_SIZE = 200 // Cache 200 color parsing results private const val TEMPLATE_CACHE_SIZE = 30 // Cache 30 template configurations } // Multi-level caching structure private val configurationCache: LruCache = LruCache(CONFIG_CACHE_SIZE) private val validationCache: ConcurrentHashMap = ConcurrentHashMap() private val colorCache: ConcurrentHashMap = ConcurrentHashMap() private val templateCache: LruCache = LruCache(TEMPLATE_CACHE_SIZE) // Performance monitoring private var configCacheHits: Long = 0 private var configCacheMisses: Long = 0 private var validationCacheHits: Long = 0 private var validationCacheMisses: Long = 0 private var colorCacheHits: Long = 0 private var colorCacheMisses: Long = 0 private var templateCacheHits: Long = 0 private var templateCacheMisses: Long = 0 /** * Gets a cached configuration or builds and caches a new one * @param params Presentation parameters * @param globalConfig Global configuration (nullable) * @return Cached or newly built PickerConfig */ fun getOrBuildConfiguration( params: LDP_PresentParams, globalConfig: LDP_ConfigParams? ): PickerConfig { val cacheKey = generateConfigurationKey(params, globalConfig) // Try to get from cache first configurationCache.get(cacheKey)?.let { cachedConfig -> configCacheHits++ Log.d(TAG, "Configuration cache hit for key: $cacheKey") return cachedConfig } // Cache miss - build new configuration configCacheMisses++ Log.d(TAG, "Configuration cache miss for key: $cacheKey") // Build configuration using cached components val config = buildConfigurationWithCache(params, globalConfig) // Cache the result configurationCache.put(cacheKey, config) return config } /** * Validates configuration with caching * @param config Configuration to validate * @return Validation result (cached or computed) */ fun validateConfiguration(config: LDP_ConfigParams): ValidationResult { val cacheKey = generateValidationKey(config) // Try to get from cache first validationCache[cacheKey]?.let { cached -> validationCacheHits++ Log.d(TAG, "Validation cache hit for key: $cacheKey") return cached } // Cache miss - perform validation validationCacheMisses++ Log.d(TAG, "Validation cache miss for key: $cacheKey") val result = performValidation(config) // Cache the result validationCache[cacheKey] = result return result } /** * Parses color with caching * @param hex Hex color string * @return Parsed color integer (cached or computed) */ fun parseColor(hex: String): Int { // Try to get from cache first colorCache[hex]?.let { cached -> colorCacheHits++ return cached } // Cache miss - parse color colorCacheMisses++ val color = try { android.graphics.Color.parseColor(hex) } catch (e: IllegalArgumentException) { throw com.margelo.nitro.lunardatepicker.exceptions.LunarDatePickerException.ThemeError("Invalid hex color: $hex") } // Cache the result colorCache[hex] = color return color } /** * Gets cached configuration template or creates and caches a new one * @param theme Theme to create template for * @param language Language to create template for * @return Cached or newly created configuration template */ fun getOrCreateTemplate( theme: LDP_CustomStyle?, language: LDP_CustomLanguage? ): ConfigurationTemplate { val cacheKey = generateTemplateKey(theme, language) // Try to get from cache first templateCache.get(cacheKey)?.let { cached -> templateCacheHits++ Log.d(TAG, "Template cache hit for key: $cacheKey") return cached } // Cache miss - create template templateCacheMisses++ Log.d(TAG, "Template cache miss for key: $cacheKey") val template = createConfigurationTemplate(theme, language) // Cache the result templateCache.put(cacheKey, template) return template } /** * Clears all caches */ fun clearAllCaches() { configurationCache.evictAll() validationCache.clear() colorCache.clear() templateCache.evictAll() Log.d(TAG, "All caches cleared") } /** * Gets comprehensive cache statistics * @return Cache performance statistics */ fun getCacheStats(): CacheStats { return CacheStats( configCacheHits = configCacheHits, configCacheMisses = configCacheMisses, validationCacheHits = validationCacheHits, validationCacheMisses = validationCacheMisses, colorCacheHits = colorCacheHits, colorCacheMisses = colorCacheMisses, templateCacheHits = templateCacheHits, templateCacheMisses = templateCacheMisses, configCacheSize = configurationCache.size(), validationCacheSize = validationCache.size, colorCacheSize = colorCache.size, templateCacheSize = templateCache.size() ) } // Private methods /** * Builds configuration using cached components */ private fun buildConfigurationWithCache( params: LDP_PresentParams, globalConfig: LDP_ConfigParams? ): PickerConfig { var config = PickerConfig.default.copy() // Apply basic configuration config = config.copy( controller = config.controller.copy( title = params.title, cancelButtonTitle = params.textCancel ) ) // Apply cached theme and language templates globalConfig?.let { globalConf -> globalConf.themes[params.theme]?.let { theme -> globalConf.languages[params.language]?.let { language -> val template = getOrCreateTemplate(theme, language) config = applyTemplate(config, template) } } // Apply other configurations config = applyTimeZone(globalConf.timeZoneOffset, config) config = applyYearRange(globalConf.yearRangeOffset, config) globalConf.monthVisibleDebounceDelaySeconds?.let { delay -> config = applyMonthVisibleDebounceDelay(delay, config) } } return config } /** * Applies configuration template to picker config */ private fun applyTemplate(config: PickerConfig, template: ConfigurationTemplate): PickerConfig { return config.copy( controller = config.controller.copy( backgroundColor = template.backgroundColor, cancelColor = template.cancelColor, titleColor = template.titleColor ), dayCell = config.dayCell.copy( dateLabelColor = template.dateLabelColor, weekendLabelColor = template.weekendLabelColor, lunarDateLabelColor = template.lunarDateLabelColor, specialDateLabelColor = template.specialDateLabelColor, priceLabelColor = template.priceLabelColor, cheapestPriceLabelColor = template.cheapestPriceLabelColor, rangeBackgroundColor = template.rangeBackgroundColor, selectedBackgroundColor = template.selectedBackgroundColor, selectedTextColor = template.selectedTextColor ), monthHeader = config.monthHeader.copy( labelColor = template.monthLabelColor, monthNames = template.monthNames ), weekView = config.weekView.copy( backgroundColor = template.weekViewBackgroundColor, weekendLabelColor = template.weekendLabelColor, weekLabelColor = template.weekLabelColor, weekdayNames = template.weekdayNames ) ) } /** * Creates configuration template from theme and language */ private fun createConfigurationTemplate( theme: LDP_CustomStyle?, language: LDP_CustomLanguage? ): ConfigurationTemplate { return ConfigurationTemplate( // Theme colors (cached parsing) backgroundColor = theme?.backgroundColor?.let { parseColor(it) } ?: android.graphics.Color.WHITE, titleColor = theme?.titleColor?.let { parseColor(it) } ?: android.graphics.Color.BLACK, cancelColor = theme?.cancelColor?.let { parseColor(it) } ?: android.graphics.Color.BLUE, dateLabelColor = theme?.dateLabelColor?.let { parseColor(it) } ?: android.graphics.Color.BLACK, weekendLabelColor = theme?.weekendLabelColor?.let { parseColor(it) } ?: parseColor("#FF9500"), lunarDateLabelColor = theme?.lunarDateLabelColor?.let { parseColor(it) } ?: parseColor("#8E8E93"), specialDateLabelColor = theme?.specialDayLabelColor?.let { parseColor(it) } ?: parseColor("#FF9500"), priceLabelColor = theme?.priceLabelColor?.let { parseColor(it) } ?: android.graphics.Color.BLACK, cheapestPriceLabelColor = theme?.cheapestPriceLabelColor?.let { parseColor(it) } ?: parseColor("#34C759"), rangeBackgroundColor = theme?.rangeBackgroundColor?.let { parseColor(it) } ?: parseColor("#E5F3FF"), selectedBackgroundColor = theme?.selectedBackgroundColor?.let { parseColor(it) } ?: parseColor("#007AFF"), selectedTextColor = theme?.selectedTextColor?.let { parseColor(it) } ?: android.graphics.Color.WHITE, monthLabelColor = theme?.monthLabelColor?.let { parseColor(it) } ?: android.graphics.Color.BLACK, weekViewBackgroundColor = theme?.weekViewBackgroundColor?.let { parseColor(it) } ?: android.graphics.Color.WHITE, weekLabelColor = theme?.dateLabelColor?.let { parseColor(it) } ?: android.graphics.Color.BLACK, // Language strings monthNames = language?.monthNames ?: arrayOf( "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ), weekdayNames = language?.weekdayNames ?: arrayOf( "T2", "T3", "T4", "T5", "T6", "T7", "CN" ) ) } /** * Performs validation and returns result */ private fun performValidation(config: LDP_ConfigParams): ValidationResult { return try { // Validate themes config.themes.forEach { (_, theme) -> validateHexColor(theme.backgroundColor) validateHexColor(theme.titleColor) validateHexColor(theme.cancelColor) validateHexColor(theme.dateLabelColor) validateHexColor(theme.lunarDateLabelColor) validateHexColor(theme.selectedTextColor) validateHexColor(theme.weekendLabelColor) validateHexColor(theme.specialDayLabelColor) validateHexColor(theme.monthLabelColor) validateHexColor(theme.weekViewBackgroundColor) validateHexColor(theme.selectedBackgroundColor) validateHexColor(theme.rangeBackgroundColor) } // Validate languages config.languages.forEach { (_, language) -> if (language.weekdayNames.size != 7) { throw IllegalArgumentException("Weekday names must contain exactly 7 items") } if (language.monthNames.size != 12) { throw IllegalArgumentException("Month names must contain exactly 12 items") } } ValidationResult.Valid } catch (e: Exception) { ValidationResult.Invalid(e.message ?: "Unknown validation error") } } /** * Validates hex color format */ private fun validateHexColor(hex: String) { val hexRegex = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".toRegex() if (!hexRegex.matches(hex)) { throw IllegalArgumentException("Invalid hex color: $hex") } } /** * Applies timezone configuration */ private fun applyTimeZone(offset: Double, config: PickerConfig): PickerConfig { val timeZone = java.util.TimeZone.getTimeZone("GMT${if (offset >= 0) "+" else ""}${offset.toInt()}") return config.copy( calendar = config.calendar.copy( timeZone = timeZone ) ) } /** * Applies year range configuration */ private fun applyYearRange(offset: Double, config: PickerConfig): PickerConfig { return config.copy( yearRangeOffset = offset.toInt() ) } /** * Applies month visible debounce delay configuration */ private fun applyMonthVisibleDebounceDelay(delay: Double, config: PickerConfig): PickerConfig { return config.copy( monthVisibleDebounceDelaySeconds = delay ) } /** * Generates cache key for configuration */ private fun generateConfigurationKey( params: LDP_PresentParams, globalConfig: LDP_ConfigParams? ): String { val keyBuilder = StringBuilder() keyBuilder.append(params.theme).append("|") keyBuilder.append(params.language).append("|") keyBuilder.append(params.title).append("|") keyBuilder.append(params.textCancel).append("|") globalConfig?.let { config -> keyBuilder.append(config.timeZoneOffset).append("|") keyBuilder.append(config.yearRangeOffset).append("|") keyBuilder.append(config.monthVisibleDebounceDelaySeconds).append("|") } return generateMD5Hash(keyBuilder.toString()) } /** * Generates cache key for validation */ private fun generateValidationKey(config: LDP_ConfigParams): String { val keyBuilder = StringBuilder() // Add theme data config.themes.forEach { (key, theme) -> keyBuilder.append(key).append(":") keyBuilder.append(theme.backgroundColor).append(",") keyBuilder.append(theme.titleColor).append(",") keyBuilder.append(theme.cancelColor).append(",") keyBuilder.append(theme.dateLabelColor).append(",") keyBuilder.append(theme.lunarDateLabelColor).append(",") keyBuilder.append(theme.selectedTextColor).append(",") keyBuilder.append(theme.weekendLabelColor).append(",") keyBuilder.append(theme.specialDayLabelColor).append(",") keyBuilder.append(theme.monthLabelColor).append(",") keyBuilder.append(theme.weekViewBackgroundColor).append(",") keyBuilder.append(theme.selectedBackgroundColor).append(",") keyBuilder.append(theme.rangeBackgroundColor).append("|") } // Add language data config.languages.forEach { (key, language) -> keyBuilder.append(key).append(":") keyBuilder.append(language.weekdayNames.joinToString(",")) keyBuilder.append(language.monthNames.joinToString(",")).append("|") } return generateMD5Hash(keyBuilder.toString()) } /** * Generates cache key for template */ private fun generateTemplateKey(theme: LDP_CustomStyle?, language: LDP_CustomLanguage?): String { val keyBuilder = StringBuilder() theme?.let { t -> keyBuilder.append(t.backgroundColor).append(",") keyBuilder.append(t.titleColor).append(",") keyBuilder.append(t.cancelColor).append(",") keyBuilder.append(t.dateLabelColor).append(",") keyBuilder.append(t.lunarDateLabelColor).append(",") keyBuilder.append(t.selectedTextColor).append(",") keyBuilder.append(t.weekendLabelColor).append(",") keyBuilder.append(t.specialDayLabelColor).append(",") keyBuilder.append(t.monthLabelColor).append(",") keyBuilder.append(t.weekViewBackgroundColor).append(",") keyBuilder.append(t.selectedBackgroundColor).append(",") keyBuilder.append(t.rangeBackgroundColor).append("|") } language?.let { l -> keyBuilder.append(l.weekdayNames.joinToString(",")) keyBuilder.append(l.monthNames.joinToString(",")).append("|") } return generateMD5Hash(keyBuilder.toString()) } /** * Generates MD5 hash for cache key */ private fun generateMD5Hash(input: String): String { val md = MessageDigest.getInstance("MD5") val digest = md.digest(input.toByteArray()) return digest.joinToString("") { "%02x".format(it) } } /** * Configuration template data class */ data class ConfigurationTemplate( val backgroundColor: Int, val titleColor: Int, val cancelColor: Int, val dateLabelColor: Int, val weekendLabelColor: Int, val lunarDateLabelColor: Int, val specialDateLabelColor: Int, val priceLabelColor: Int, val cheapestPriceLabelColor: Int, val rangeBackgroundColor: Int, val selectedBackgroundColor: Int, val selectedTextColor: Int, val monthLabelColor: Int, val weekViewBackgroundColor: Int, val weekLabelColor: Int, val monthNames: Array, val weekdayNames: Array ) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as ConfigurationTemplate if (backgroundColor != other.backgroundColor) return false if (titleColor != other.titleColor) return false if (cancelColor != other.cancelColor) return false if (dateLabelColor != other.dateLabelColor) return false if (weekendLabelColor != other.weekendLabelColor) return false if (lunarDateLabelColor != other.lunarDateLabelColor) return false if (specialDateLabelColor != other.specialDateLabelColor) return false if (priceLabelColor != other.priceLabelColor) return false if (cheapestPriceLabelColor != other.cheapestPriceLabelColor) return false if (rangeBackgroundColor != other.rangeBackgroundColor) return false if (selectedBackgroundColor != other.selectedBackgroundColor) return false if (selectedTextColor != other.selectedTextColor) return false if (monthLabelColor != other.monthLabelColor) return false if (weekViewBackgroundColor != other.weekViewBackgroundColor) return false if (weekLabelColor != other.weekLabelColor) return false if (!monthNames.contentEquals(other.monthNames)) return false if (!weekdayNames.contentEquals(other.weekdayNames)) return false return true } override fun hashCode(): Int { var result = backgroundColor result = 31 * result + titleColor result = 31 * result + cancelColor result = 31 * result + dateLabelColor result = 31 * result + weekendLabelColor result = 31 * result + lunarDateLabelColor result = 31 * result + specialDateLabelColor result = 31 * result + priceLabelColor result = 31 * result + cheapestPriceLabelColor result = 31 * result + rangeBackgroundColor result = 31 * result + selectedBackgroundColor result = 31 * result + selectedTextColor result = 31 * result + monthLabelColor result = 31 * result + weekViewBackgroundColor result = 31 * result + weekLabelColor result = 31 * result + monthNames.contentHashCode() result = 31 * result + weekdayNames.contentHashCode() return result } } /** * Validation result sealed class */ sealed class ValidationResult { object Valid : ValidationResult() data class Invalid(val error: String) : ValidationResult() } /** * Cache statistics data class */ data class CacheStats( val configCacheHits: Long, val configCacheMisses: Long, val validationCacheHits: Long, val validationCacheMisses: Long, val colorCacheHits: Long, val colorCacheMisses: Long, val templateCacheHits: Long, val templateCacheMisses: Long, val configCacheSize: Int, val validationCacheSize: Int, val colorCacheSize: Int, val templateCacheSize: Int ) { fun getConfigCacheHitRate(): Double { val total = configCacheHits + configCacheMisses return if (total > 0) configCacheHits.toDouble() / total else 0.0 } fun getValidationCacheHitRate(): Double { val total = validationCacheHits + validationCacheMisses return if (total > 0) validationCacheHits.toDouble() / total else 0.0 } fun getColorCacheHitRate(): Double { val total = colorCacheHits + colorCacheMisses return if (total > 0) colorCacheHits.toDouble() / total else 0.0 } fun getTemplateCacheHitRate(): Double { val total = templateCacheHits + templateCacheMisses return if (total > 0) templateCacheHits.toDouble() / total else 0.0 } override fun toString(): String { return "ConfigurationCache Stats: " + "Config(${getConfigCacheHitRate() * 100}% hit rate, ${configCacheSize} cached), " + "Validation(${getValidationCacheHitRate() * 100}% hit rate, ${validationCacheSize} cached), " + "Color(${getColorCacheHitRate() * 100}% hit rate, ${colorCacheSize} cached), " + "Template(${getTemplateCacheHitRate() * 100}% hit rate, ${templateCacheSize} cached)" } } }