StaticTartanStateEvaluator.java

package tartan.smarthome.resources;

import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import tartan.smarthome.resources.iotcontroller.IoTValues;

public class StaticTartanStateEvaluator implements TartanStateEvaluator {

    private final Supplier<Long> timeProvider;
    private static final Logger LOGGER = LoggerFactory.getLogger(StaticTartanStateEvaluator.class);

    public StaticTartanStateEvaluator(Supplier<Long> timeProvider) {
        this.timeProvider = timeProvider;
    }

    /**
     * Helper to format log entries with timestamps
     * 
     * @param entry The log message to format
     * @return A formatted log entry with timestamp prefix
     */
    private String formatLogEntry(String entry) {
        Long timeStamp = timeProvider.get();
        SimpleDateFormat sdf = new SimpleDateFormat("MMM dd,yyyy HH:mm");
        return "[" + sdf.format(new Date(timeStamp)) + "]: " + entry + "\n";
    }

    /**
     * Helper to get current time (both real and faked)
     * 
     * @return current hour in the form of 0-23 (0 midnight)
     */
    private Long getCurrentTime() {
        return (timeProvider.get() / (1000 * 60 * 60)) % 24;
    }

    /**
     * Ensure the requested state is permitted. This method checks each state
     * variable to ensure that the house remains in a consistent state.
     *
     * @param state The new state to evaluate
     * @param log   The log of state evaluations
     * @return The evaluated state
     */
    @SuppressWarnings("null")
    @Override
    public Map<String, Object> evaluateState(Map<String, Object> inState, StringBuilder log) {

        // state variables that reflect the current configuration of the house
        Integer tempReading = null; // the current temperature
        Integer targetTempSetting = null; // the user-desired temperature setting
        Boolean doorState = null; // the state of the door (true if open, false if closed)
        Boolean lightState = null; // the state of the light (true if on, false if off)
        Boolean proximityState = null; // the state of the proximity sensor (true of house occupied, false if vacant)
        Boolean alarmState = null; // the alarm state (true if enabled, false if disabled)
        Boolean humidifierState = null; // the humidifier state (true if on, false if off)
        Boolean heaterOnState = false; // the heater state (true if on, false if off)
        Boolean chillerOnState = false; // the chiller state (true if on, false if off)
        Boolean alarmActiveState = null; // the alarm active state (true if alarm sounding, false if alarm not sounding)
        Boolean awayTimerState = false; // assume that the away timer did not trigger this evaluation
        String alarmPasscode = null; // the alarm passcode
        String hvacState = null; // the HVAC mode setting, either Heater or Chiller
        String givenPasscode = ""; // the passcode entered by the user

        // smart lock variables
        Boolean smartLockState = null; // the state of the smart lock (true if locked, false if unlocked)
        String smartLockPasscode = null; // passcode for the smart lock
        String enteredPasscode = ""; // The smart lock passcode entered by the user
        Boolean nightLockState = false; // the night lock state (true if enabled, false if disabled)
        String nightStartTime = null; // configured start time for night mode
        String nightEndTime = null; // configured end time for night mode
        String smartProxyId = null; // the ID for keyless entry
        String givenProxyId = ""; // the ID 'detected' by the sensor
        Boolean intruderDetected = null; // whether an intruder has been detected
        Boolean allClear = true; // whether all clear (true if all clear, false if not all clear)
        Boolean requiresPasscode = true; // if lock requires a passcode (true if requires passcode, false if not)
        String experimentGroup = "1"; // the group for the A/B test

        LOGGER.debug("Evaluating new state statically");

        for (String key : inState.keySet()) {
            switch (key) {
                case IoTValues.TEMP_READING -> tempReading = (Integer) inState.get(key);
                case IoTValues.TARGET_TEMP -> targetTempSetting = (Integer) inState.get(key);
                case IoTValues.DOOR_STATE -> doorState = (Boolean) inState.get(key);
                case IoTValues.LIGHT_STATE -> lightState = (Boolean) inState.get(key);
                case IoTValues.PROXIMITY_STATE -> proximityState = (Boolean) inState.get(key);
                case IoTValues.ALARM_STATE -> alarmState = (Boolean) inState.get(key);
                case IoTValues.HUMIDIFIER_STATE -> humidifierState = (Boolean) inState.get(key);
                case IoTValues.HVAC_MODE -> hvacState = (String) inState.get(key);
                case IoTValues.ALARM_PASSCODE -> alarmPasscode = (String) inState.get(key);
                case IoTValues.GIVEN_PASSCODE -> givenPasscode = (String) inState.get(key);
                case IoTValues.AWAY_TIMER -> awayTimerState = (Boolean) inState.getOrDefault(key, false);
                case IoTValues.ALARM_ACTIVE -> alarmActiveState = (Boolean) inState.get(key);
                case IoTValues.SMART_LOCK_STATE -> smartLockState = (Boolean) inState.get(key);
                case IoTValues.SMART_LOCK_PASSCODE -> smartLockPasscode = (String) inState.get(key);
                case IoTValues.ENTERED_PASSCODE -> enteredPasscode = (String) inState.get(key);
                case IoTValues.SMART_LOCK_REQUIRES_PASSCODE -> requiresPasscode = (Boolean) inState.get(key);
                case IoTValues.NIGHT_START_TIME -> nightStartTime = inState.get(key).toString();
                case IoTValues.NIGHT_END_TIME -> nightEndTime = inState.get(key).toString();
                case IoTValues.SMART_PROXY_ID -> smartProxyId = (String) inState.get(key);
                case IoTValues.GIVEN_PROXY_ID -> givenProxyId = (String) inState.get(key);
                case IoTValues.INTRUDER_DETECTED -> intruderDetected = (Boolean) inState.get(key);
                case IoTValues.ALL_CLEAR -> allClear = (Boolean) inState.get(key);
                default -> log.append(formatLogEntry("Unknown state variable: " + key));
            }
        }

        if (Boolean.TRUE.equals(awayTimerState)) {
            lightState = false;
            doorState = false;
            alarmState = true;
            awayTimerState = false;
            smartLockState = true;
            log.append(formatLogEntry("Away timer activated - securing house"));
        }

        if (Boolean.TRUE.equals(lightState)) {
            // The light was activated
            if (Boolean.FALSE.equals(proximityState)) {
                log.append(formatLogEntry("Cannot turn on light because user not home"));
                lightState = false;
            } else {
                log.append(formatLogEntry("Light on"));
            }
        } else {
            log.append(formatLogEntry("Light off"));
        }

        // Handle door state when door is open
        if (Boolean.TRUE.equals(doorState)) {
            if (Boolean.FALSE.equals(proximityState)) {
                // R3: If house is vacant, automatically close the door
                doorState = false;
                log.append(formatLogEntry("Closed door because house vacant"));

                // R2: If alarm is enabled when door opens in vacant house, trigger alarm
                if (Boolean.TRUE.equals(alarmState)) {
                    log.append(formatLogEntry("Break in detected: Activating alarm"));
                    alarmActiveState = true;
                    intruderDetected = true;
                }
            } else {
                log.append(formatLogEntry("Door open"));
            }
        }
        // The door is now closed
        else if (Boolean.FALSE.equals(doorState)) {
            // the door is closed - if the house is suddenly occupied this is a break-in
            if (Boolean.TRUE.equals(alarmState) && Boolean.TRUE.equals(proximityState)) {
                log.append(formatLogEntry("Break in detected: Activating alarm"));
                alarmActiveState = true;
            } else {
                log.append(formatLogEntry("Closed door"));
            }
        }

        // the user has arrived
        if (Boolean.TRUE.equals(proximityState) && Boolean.FALSE.equals(awayTimerState)) {
            log.append(formatLogEntry("House is occupied"));

            // if the alarm has been disabled, then turn on the light for the user
            if (Boolean.FALSE.equals(lightState) && Boolean.FALSE.equals(alarmState)) {
                lightState = true;
                log.append(formatLogEntry("Turning on light"));
            }
        }

        // set the alarm
        if (Boolean.TRUE.equals(alarmState)) {
            log.append(formatLogEntry("Alarm enabled"));

        } else if (Boolean.FALSE.equals(alarmState)) { // attempt to disable alarm

            if (Boolean.FALSE.equals(proximityState)) {
                alarmState = true;
                log.append(formatLogEntry("Cannot disable the alarm, house is empty"));
            }

            if (Boolean.TRUE.equals(alarmActiveState)) {
                if (!givenPasscode.equals(alarmPasscode)) {
                    log.append(formatLogEntry("Cannot disable alarm, invalid passcode given"));
                    alarmState = true;

                } else {
                    log.append(formatLogEntry("Correct passcode entered, disabled alarm"));
                    alarmActiveState = false;
                }
            }

            // If house is occupied and wrong passcode is entered, enable the alarm
            else if (Boolean.TRUE.equals(proximityState) && !givenPasscode.equals(alarmPasscode)) {
                alarmState = true;
                log.append(formatLogEntry("Cannot disable the alarm, wrong passcode"));
            }
        }

        if (Boolean.FALSE.equals(alarmState)) {
            log.append(formatLogEntry("Alarm disabled"));
            alarmActiveState = false;
        }

        // manage the HVAC control
        if (tempReading < targetTempSetting) {
            heaterOnState = true;
            chillerOnState = false;
            humidifierState = false;
            hvacState = "Heater";
            log.append(formatLogEntry(
                    "Heater activated - Target: " + targetTempSetting + "F, Current: " + tempReading + "F"));
        } else if (tempReading > targetTempSetting) {
            heaterOnState = false;
            chillerOnState = true;
            hvacState = "Chiller";
            log.append(formatLogEntry(
                    "Cooling activated - Target: " + targetTempSetting + "F, Current: " + tempReading + "F"));
        } else {
            heaterOnState = false;
            chillerOnState = false;
        }

        // NIGHT LOCK LOGIC
        Long nightStart = Long.valueOf(nightStartTime);
        Long nightEnd = Long.valueOf(nightEndTime);
        Long currentTime = getCurrentTime();

        if (nightStart <= nightEnd) {
            if (currentTime >= nightStart && currentTime < nightEnd) {
                log.append(formatLogEntry("Night lock ON - Current Time b/w : " + nightStart + " & " + nightEnd));
                nightLockState = true;
                smartLockState = true;
            } else {
                log.append(formatLogEntry("Night lock OFF - Outside night hours"));
                nightLockState = false;
            }
        } else if (currentTime >= nightStart || currentTime < nightEnd) {
            log.append(formatLogEntry("Night lock ON - Current Time b/w : " + nightStart + " & " + nightEnd));
            nightLockState = true;
            smartLockState = true;
        } else {
            log.append(formatLogEntry("Night lock OFF - Outside night hours"));
            nightLockState = false;
        }

        // Intruder defense logic
        if (Boolean.TRUE.equals(intruderDetected)) {
            smartLockState = true;
            doorState = false;
            alarmActiveState = true;
            lightState = true;
            log.append(formatLogEntry("ALERT: Possible intruder detected - Full security response activated"));

            if (Boolean.TRUE.equals(allClear) && givenPasscode != null && givenPasscode.equals(alarmPasscode)) {
                intruderDetected = false;
                log.append(formatLogEntry("All clear signal received with proper authorization"));
            }
        }

        // Keyless Entry Logic
        if (smartProxyId != null && givenProxyId != null && smartProxyId.equals(givenProxyId)) {
            smartLockState = false;
            log.append(formatLogEntry("Keyless entry authorized - Smart lock unlocked"));
        }

        // Smart lock logic
        if (smartLockState != null) {
            if (Boolean.TRUE.equals(nightLockState)) {
                smartLockState = true;
            } else if (Boolean.TRUE.equals(smartLockState)) {
                if (Boolean.FALSE.equals(requiresPasscode)) {
                    smartLockState = false;
                    log.append(formatLogEntry("Smart lock unlocked - no passcode required"));
                } else if (enteredPasscode != null && smartLockPasscode != null &&
                        enteredPasscode.equals(smartLockPasscode)) {
                    smartLockState = false;
                    log.append(formatLogEntry("Smart lock unlocked with valid passcode"));
                }
            }
        }

        Map<String, Object> newState = new HashMap<>();
        newState.put(IoTValues.DOOR_STATE, doorState);
        newState.put(IoTValues.AWAY_TIMER, awayTimerState);
        newState.put(IoTValues.LIGHT_STATE, lightState);
        newState.put(IoTValues.PROXIMITY_STATE, proximityState);
        newState.put(IoTValues.ALARM_STATE, alarmState);
        newState.put(IoTValues.HUMIDIFIER_STATE, humidifierState);
        newState.put(IoTValues.HEATER_STATE, heaterOnState);
        newState.put(IoTValues.CHILLER_STATE, chillerOnState);
        newState.put(IoTValues.ALARM_ACTIVE, alarmActiveState);
        newState.put(IoTValues.HVAC_MODE, hvacState);
        newState.put(IoTValues.ALARM_PASSCODE, alarmPasscode);
        newState.put(IoTValues.GIVEN_PASSCODE, givenPasscode);
        newState.put(IoTValues.SMART_LOCK_STATE, smartLockState);
        newState.put(IoTValues.NIGHT_LOCK_STATE, nightLockState);
        newState.put(IoTValues.SMART_LOCK_PASSCODE, smartLockPasscode);
        newState.put(IoTValues.NIGHT_START_TIME, nightStartTime);
        newState.put(IoTValues.NIGHT_END_TIME, nightEndTime);
        newState.put(IoTValues.INTRUDER_DETECTED, intruderDetected);
        newState.put(IoTValues.SMART_PROXY_ID, smartProxyId);
        newState.put(IoTValues.GIVEN_PROXY_ID, givenProxyId);
        newState.put(IoTValues.ALL_CLEAR, allClear);
        newState.put(IoTValues.SMART_LOCK_REQUIRES_PASSCODE, requiresPasscode);
        newState.put(IoTValues.ENTERED_PASSCODE, enteredPasscode);
        newState.put(IoTValues.EXPERIMENT_GROUP, experimentGroup);

        return newState;
    }
}