|
| 1 | +package ch.fhnw.pepper_realtime.manager |
| 2 | + |
| 3 | +import android.util.Log |
| 4 | +import ch.fhnw.pepper_realtime.data.EventRule |
| 5 | +import ch.fhnw.pepper_realtime.data.MatchedRule |
| 6 | +import ch.fhnw.pepper_realtime.data.RulePersistence |
| 7 | +import ch.fhnw.pepper_realtime.data.RuleActionType |
| 8 | +import ch.fhnw.pepper_realtime.service.EventRuleEngine |
| 9 | +import ch.fhnw.pepper_realtime.ui.EventRulesState |
| 10 | +import kotlinx.coroutines.flow.MutableStateFlow |
| 11 | +import kotlinx.coroutines.flow.StateFlow |
| 12 | +import kotlinx.coroutines.flow.asStateFlow |
| 13 | +import kotlinx.coroutines.flow.update |
| 14 | +import javax.inject.Inject |
| 15 | +import javax.inject.Singleton |
| 16 | + |
| 17 | +/** |
| 18 | + * Manager for event rules state and operations. |
| 19 | + * Extracted from ChatViewModel for better separation of concerns. |
| 20 | + */ |
| 21 | +@Singleton |
| 22 | +class EventRulesManager @Inject constructor( |
| 23 | + private val eventRuleEngine: EventRuleEngine, |
| 24 | + private val rulePersistence: RulePersistence |
| 25 | +) { |
| 26 | + |
| 27 | + companion object { |
| 28 | + private const val TAG = "EventRulesManager" |
| 29 | + } |
| 30 | + |
| 31 | + // Expose eventRuleEngine for external access (e.g., for evaluate() calls from PerceptionService) |
| 32 | + val engine: EventRuleEngine get() = eventRuleEngine |
| 33 | + |
| 34 | + private val _state = MutableStateFlow(EventRulesState()) |
| 35 | + val state: StateFlow<EventRulesState> = _state.asStateFlow() |
| 36 | + |
| 37 | + /** |
| 38 | + * Callback interface for rule actions that affect the chat. |
| 39 | + */ |
| 40 | + interface RuleActionHandler { |
| 41 | + fun onAddEventMessage(matchedRule: MatchedRule) |
| 42 | + fun onSendToRealtimeAPI(text: String, requestResponse: Boolean, allowInterrupt: Boolean) |
| 43 | + } |
| 44 | + |
| 45 | + private var ruleActionHandler: RuleActionHandler? = null |
| 46 | + |
| 47 | + /** |
| 48 | + * Set the handler for rule actions. |
| 49 | + * Should be called during ViewModel initialization. |
| 50 | + */ |
| 51 | + fun setRuleActionHandler(handler: RuleActionHandler) { |
| 52 | + ruleActionHandler = handler |
| 53 | + } |
| 54 | + |
| 55 | + /** |
| 56 | + * Initialize the event rule engine with persisted rules. |
| 57 | + */ |
| 58 | + fun initialize() { |
| 59 | + val rules = rulePersistence.loadRules() |
| 60 | + eventRuleEngine.loadRules(rules) |
| 61 | + _state.update { it.copy(rules = rules) } |
| 62 | + |
| 63 | + // Set up listener for matched rules |
| 64 | + eventRuleEngine.setListener(object : EventRuleEngine.RuleMatchListener { |
| 65 | + override fun onRuleMatched(matchedRule: MatchedRule) { |
| 66 | + handleRuleAction(matchedRule) |
| 67 | + } |
| 68 | + }) |
| 69 | + |
| 70 | + Log.i(TAG, "Event rules initialized with ${rules.size} rules") |
| 71 | + } |
| 72 | + |
| 73 | + /** |
| 74 | + * Set the robot state provider for rule condition checks. |
| 75 | + */ |
| 76 | + fun setRobotStateProvider(provider: EventRuleEngine.RobotStateProvider) { |
| 77 | + eventRuleEngine.setRobotStateProvider(provider) |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * Handle a matched rule by executing the appropriate action. |
| 82 | + */ |
| 83 | + private fun handleRuleAction(matchedRule: MatchedRule) { |
| 84 | + Log.i(TAG, "Handling rule action: ${matchedRule.rule.name} (${matchedRule.rule.actionType})") |
| 85 | + |
| 86 | + // Add to recent triggered rules for UI feedback |
| 87 | + _state.update { state -> |
| 88 | + val newRecent = (listOf(matchedRule) + state.recentTriggeredRules).take(10) |
| 89 | + state.copy(recentTriggeredRules = newRecent) |
| 90 | + } |
| 91 | + |
| 92 | + // Notify handler to add event message to chat |
| 93 | + ruleActionHandler?.onAddEventMessage(matchedRule) |
| 94 | + |
| 95 | + // Execute the action via callback |
| 96 | + when (matchedRule.rule.actionType) { |
| 97 | + RuleActionType.INTERRUPT_AND_RESPOND -> { |
| 98 | + ruleActionHandler?.onSendToRealtimeAPI( |
| 99 | + text = matchedRule.resolvedTemplate, |
| 100 | + requestResponse = true, |
| 101 | + allowInterrupt = true |
| 102 | + ) |
| 103 | + } |
| 104 | + RuleActionType.APPEND_AND_RESPOND -> { |
| 105 | + ruleActionHandler?.onSendToRealtimeAPI( |
| 106 | + text = matchedRule.resolvedTemplate, |
| 107 | + requestResponse = true, |
| 108 | + allowInterrupt = false |
| 109 | + ) |
| 110 | + } |
| 111 | + RuleActionType.SILENT_UPDATE -> { |
| 112 | + ruleActionHandler?.onSendToRealtimeAPI( |
| 113 | + text = matchedRule.resolvedTemplate, |
| 114 | + requestResponse = false, |
| 115 | + allowInterrupt = false |
| 116 | + ) |
| 117 | + } |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + // ==================== UI State Methods ==================== |
| 122 | + |
| 123 | + fun showEventRules() { |
| 124 | + _state.update { it.copy(isVisible = true) } |
| 125 | + } |
| 126 | + |
| 127 | + fun hideEventRules() { |
| 128 | + _state.update { it.copy(isVisible = false) } |
| 129 | + } |
| 130 | + |
| 131 | + fun toggleEventRules() { |
| 132 | + _state.update { it.copy(isVisible = !it.isVisible) } |
| 133 | + } |
| 134 | + |
| 135 | + // ==================== CRUD Operations ==================== |
| 136 | + |
| 137 | + fun addEventRule(rule: EventRule) { |
| 138 | + eventRuleEngine.addRule(rule) |
| 139 | + saveEventRules() |
| 140 | + _state.update { it.copy(rules = eventRuleEngine.getRules()) } |
| 141 | + } |
| 142 | + |
| 143 | + fun updateEventRule(rule: EventRule) { |
| 144 | + eventRuleEngine.updateRule(rule) |
| 145 | + saveEventRules() |
| 146 | + _state.update { it.copy(rules = eventRuleEngine.getRules()) } |
| 147 | + } |
| 148 | + |
| 149 | + fun deleteEventRule(ruleId: String) { |
| 150 | + eventRuleEngine.removeRule(ruleId) |
| 151 | + saveEventRules() |
| 152 | + _state.update { it.copy(rules = eventRuleEngine.getRules()) } |
| 153 | + } |
| 154 | + |
| 155 | + fun toggleEventRuleEnabled(ruleId: String) { |
| 156 | + val rules = eventRuleEngine.getRules() |
| 157 | + val rule = rules.find { it.id == ruleId } ?: return |
| 158 | + val updatedRule = rule.copy(enabled = !rule.enabled) |
| 159 | + updateEventRule(updatedRule) |
| 160 | + } |
| 161 | + |
| 162 | + private fun saveEventRules() { |
| 163 | + rulePersistence.saveRules(eventRuleEngine.getRules()) |
| 164 | + } |
| 165 | + |
| 166 | + fun resetEventRulesToDefaults() { |
| 167 | + rulePersistence.resetToDefaults() |
| 168 | + val rules = rulePersistence.loadRules() |
| 169 | + eventRuleEngine.loadRules(rules) |
| 170 | + eventRuleEngine.resetCooldowns() |
| 171 | + _state.update { it.copy(rules = rules) } |
| 172 | + } |
| 173 | + |
| 174 | + // ==================== Import/Export ==================== |
| 175 | + |
| 176 | + fun exportEventRules(): String { |
| 177 | + return rulePersistence.exportToJson() |
| 178 | + } |
| 179 | + |
| 180 | + fun importEventRules(json: String, merge: Boolean = false): Int { |
| 181 | + val count = rulePersistence.importFromJson(json, merge) |
| 182 | + if (count >= 0) { |
| 183 | + val rules = rulePersistence.loadRules() |
| 184 | + eventRuleEngine.loadRules(rules) |
| 185 | + _state.update { it.copy(rules = rules) } |
| 186 | + } |
| 187 | + return count |
| 188 | + } |
| 189 | + |
| 190 | + fun setEditingRule(rule: EventRule?) { |
| 191 | + _state.update { it.copy(editingRule = rule) } |
| 192 | + } |
| 193 | +} |
0 commit comments