// © Copyright CERN, Riga Technical University and University of Liverpool 2020.
// All rights not expressly granted are reserved. 
// 
// This file is part of hev-sw.
// 
// hev-sw is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public Licence as published by the Free
// Software Foundation, either version 3 of the Licence, or (at your option)
// any later version.
// 
// hev-sw 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 General Public Licence
// for more details.
// 
// You should have received a copy of the GNU General Public License along
// with hev-sw. If not, see <http://www.gnu.org/licenses/>.
// 
// The authors would like to acknowledge the much appreciated support
// of all those involved with the High Energy Ventilator project
// (https://hev.web.cern.ch/).


#include "AlarmLoop.h"

#include "BreathingLoop.h"

AlarmLoop::AlarmLoop(BreathingLoop *bl) //, Adafruit_TPA2016* tpa2016)
{
    _breathing_loop = bl;

    // make sure starting value is between thresholds
    for (uint8_t alarm_num = 0; alarm_num < ALARM_CODES::ALARMS_COUNT; alarm_num++) {
        _alarms.values[alarm_num] = (_alarms.thresholds_max[alarm_num] + _alarms.thresholds_min[alarm_num]) / 2;
    }

    setBatteryThresholds();

    //_av_controller.setupVolumeControl(tpa2016);
    setVolume(ALARM_VOLUME_LEVEL::LOUDEST);
}

AlarmLoop::~AlarmLoop()
{;}

bool AlarmLoop::setAlarmThresholdMin(ALARM_CODES alarm_code, float threshold_min) {
    if (_alarms.thresholds_absolute_min[alarm_code] > threshold_min) return false;

    _alarms.thresholds_min[alarm_code] = threshold_min;

    return true;
}

bool AlarmLoop::setAlarmThresholdMax(ALARM_CODES alarm_code, float threshold_max) {
    if (_alarms.thresholds_absolute_max[alarm_code] < threshold_max) return false;

    _alarms.thresholds_max[alarm_code] = threshold_max;

    return true;
}

ALARM_TYPE AlarmLoop::checkThresholds() {
    _type = ALARM_TYPE::ALARM_TYPE_UNKNOWN;
    bool active = false;
    bool extended_high_pressure = false;
    for (uint8_t alarm_num = 0; alarm_num < ALARM_CODES::ALARMS_COUNT; alarm_num++) {
        bool alarm_was_active = _alarms.actives[alarm_num];

        bool alarm_will_be_active = ( _alarms.values[alarm_num] < _alarms.thresholds_min[alarm_num]
                                     || _alarms.values[alarm_num] > _alarms.thresholds_max[alarm_num]);

        if (alarm_will_be_active) {
            // Some alarms need special handling but, should they activate,
            // mark appropriate flags.
            switch (alarm_num) {
                case ALARM_CODES::CHECK_P_PATIENT:
                    // High patient pressure could be due to a cough, use a heuristic to
                    // avoid triggering alarm spuriously.
                    if (shouldPatientPressureAlarmTrigger(alarm_was_active)) {
                        active |= true;
                        _alarms.actives[alarm_num] = true;

                        uint32_t now = millis();
                        if (!alarm_was_active) _alarms.last_begin_trigger[alarm_num] = now;
                        _alarms.last_end_trigger[alarm_num] = now;
                    }
                    // If alarm remains on too long, force open exhale valve.
                    handlePatientPressureAlarmActive(alarm_was_active);
                    break;
                case ALARM_CODES::ALARM_TEST:
                    active |= true;
                    _alarms.actives[alarm_num] = true;
                    // Don't set last_end_trigger so as to avoid the 30s buffer.
                    if (!alarm_was_active) _alarms.last_begin_trigger[alarm_num] = millis();
                    break;
                default:
                    active |= true;
                    _alarms.actives[alarm_num] = true;

                    uint32_t now = millis();
                    if (!alarm_was_active) _alarms.last_begin_trigger[alarm_num] = now;
                    _alarms.last_end_trigger[alarm_num] = now;
                    break;
            }

            // If any pressure sensor detects high pressure for longer than a determined amount of
            // time, then, regardless of the priority of the alarm that sensor produces, set a high
            // piority alarm.
            if (alarm_num == ALARM_CODES::HIGH_PRESSURE
                    || alarm_num == ALARM_CODES::HIGH_PEEP
                    || alarm_num == ALARM_CODES::CHECK_P_PATIENT) {
                uint32_t now = millis();
                if (_alarms.last_begin_trigger[alarm_num] + HIGH_PRESSURE_TIME_TIL_HIGH_ALARM <= now) {
                    extended_high_pressure = true;
                    _alarms.values[ALARM_CODES::EXTENDED_HIGH_PRESSURE] = 1;
                }
            }

            // If alarm active & of higher priority than so-far-active alarms, set new
            // overall alarm priority to this alarm's priority.
            if (_alarms.actives[alarm_num] && _alarms.priorities[alarm_num] > _type) {
                _type = _alarms.priorities[alarm_num];
            }
        } else {
            _alarms.actives[alarm_num] = false;

            // Though alarm is not triggered now, if it was triggered last within
            // ALARM_PADDING ms of now, then it remains considered active - so if it is
            // of higher priority than so-far-active alarms, set new overall alarm
            // priority to this alarm's priority.
            //   Should the last time a given alarm was triggered be prior to
            //   a reset command, don't consider it active even if in that padding
            //   time.
            uint32_t now = millis();
            if (
                                   _alarms.last_end_trigger[alarm_num] >  _last_alarm_reset
                && ALARM_PADDING + _alarms.last_end_trigger[alarm_num] >= now
                &&                       _alarms.priorities[alarm_num] >  _type
            ) {
                    _type = _alarms.priorities[alarm_num];
                }
            }
        }
    

    // If no pressure alarm had been on for the determined time, then extended high pressure
    // alarm should be set to off.
    if (!extended_high_pressure) {
        _alarms.values[ALARM_CODES::EXTENDED_HIGH_PRESSURE] = 0;
    }

    // If active is false after above checks, then set an explicit no alarm state.
    if (!active) _type = ALARM_TYPE::NO_ALARM;

    return _type;
}

void AlarmLoop::fireAlarms() {
    bool is_muted = isAlarmMuted();

    AV_STYLE audio_oscil = is_muted ? AV_STYLE::PERM_OFF : AV_STYLE::OSCIL;

    switch (checkThresholds()) {
        case ALARM_TYPE::PRIORITY_LOW:
            _av_controller.setFreqs(AV_FREQUENCY::SLOW);
            //_av_controller.setToneFreq(AV_TONE_FREQ::TN_LOW);
            _av_controller.setAVs(AV_STYLE::PERM_OFF, AV_STYLE::PERM_ON , AV_STYLE::PERM_OFF, audio_oscil);
            break;
        case ALARM_TYPE::PRIORITY_MEDIUM:
            _av_controller.setFreqs(AV_FREQUENCY::NORMAL);
            //_av_controller.setToneFreq(AV_TONE_FREQ::TN_MEDIUM);
            _av_controller.setAVs(AV_STYLE::PERM_OFF, AV_STYLE::OSCIL   , AV_STYLE::PERM_OFF, audio_oscil);
            break;
        case ALARM_TYPE::PRIORITY_HIGH:
            _av_controller.setFreqs(AV_FREQUENCY::FAST);
            //_av_controller.setToneFreq(AV_TONE_FREQ::TN_HIGH);
            _av_controller.setAVs(AV_STYLE::PERM_OFF, AV_STYLE::PERM_OFF, AV_STYLE::OSCIL   , audio_oscil);
            break;
        case ALARM_TYPE::NO_ALARM:
            _av_controller.setFreqs(AV_FREQUENCY::NORMAL);
            //_av_controller.setToneFreq(AV_TONE_FREQ::TN_LOW);
            _av_controller.setAVs(AV_STYLE::PERM_ON , AV_STYLE::PERM_OFF, AV_STYLE::PERM_OFF, AV_STYLE::PERM_OFF);
            break;
        default:
            _av_controller.setFreqs(AV_FREQUENCY::NORMAL);
            //_av_controller.setToneFreq(AV_TONE_FREQ::TN_LOW);
            _av_controller.setAVs(AV_STYLE::PERM_OFF, AV_STYLE::PERM_ON , AV_STYLE::PERM_ON , audio_oscil);
            break;
    }

    _av_controller.update();
}

void AlarmLoop::muteAlarm(bool mute) {
    _mute_alarm = mute;
    if (mute) _last_mute_command = millis();
}

bool AlarmLoop::isAlarmMuted() {
    if (_mute_alarm) {
        int32_t time_remaining = getAlarmMuteTimeRemaining();
        if (time_remaining < 0) _mute_alarm = false;

        return _mute_alarm;
    }

    return false;
}

int32_t AlarmLoop::getAlarmMuteTimeRemaining() {
    if (!_mute_alarm) return 0;

    return ALARM_MUTE_PERIOD - (millis() - _last_mute_command);
}

void AlarmLoop::setVolume(ALARM_VOLUME_LEVEL vol) {
    _alarm_volume = vol;

    //switch (_alarm_volume) {
    //case (ALARM_VOLUME_LEVEL::QUIET):
        //_av_controller.setVolume(AV_BUZZER_VOLUME::QUIET);
        //break;
    //case (ALARM_VOLUME_LEVEL::LOUD):
        //_av_controller.setVolume(AV_BUZZER_VOLUME::LOUD);
        //break;
    //case (ALARM_VOLUME_LEVEL::LOUDEST):
        //_av_controller.setVolume(AV_BUZZER_VOLUME::LOUDEST);
        //break;
    //}
}

void AlarmLoop::getVolume() {
    return _alarm_volume;
}

void AlarmLoop::resetAlarms() {
    _last_alarm_reset = millis();
}

void AlarmLoop::updateValues(readings fast_data, cycle_readings cr) {
//     ALARM_CODES::ALARM_CODE_UNKNOWN
     setAlarmValue(ALARM_CODES::APNEA,           cr.apnea_index);
     setAlarmValue(ALARM_CODES::CHECK_P_PATIENT, fast_data.pressure_patient);
     setAlarmValue(ALARM_CODES::HIGH_FIO2,       cr.fiO2_percent);
     setAlarmValue(ALARM_CODES::HIGH_PRESSURE,   fast_data.pressure_patient);
     setAlarmValue(ALARM_CODES::HIGH_RR,         cr.respiratory_rate);
     setAlarmValue(ALARM_CODES::HIGH_VTE,        cr.exhaled_tidal_volume);
     setAlarmValue(ALARM_CODES::LOW_VTE,         cr.exhaled_tidal_volume);
     setAlarmValue(ALARM_CODES::HIGH_VTI,        cr.inhaled_tidal_volume);
     setAlarmValue(ALARM_CODES::LOW_VTI,         cr.inhaled_tidal_volume);
     setAlarmValue(ALARM_CODES::HIGH_VME,        cr.exhaled_tidal_volume * cr.respiratory_rate);
     setAlarmValue(ALARM_CODES::LOW_VME,         cr.exhaled_tidal_volume * cr.respiratory_rate);
     setAlarmValue(ALARM_CODES::HIGH_VMI,        cr.inhaled_tidal_volume * cr.respiratory_rate);
     setAlarmValue(ALARM_CODES::LOW_VMI,         cr.inhaled_tidal_volume * cr.respiratory_rate);
     setAlarmValue(ALARM_CODES::LOW_FIO2,        cr.fiO2_percent);
     setAlarmValue(ALARM_CODES::HIGH_PEEP,       cr.peep);
     setAlarmValue(ALARM_CODES::LOW_PEEP,        cr.peep);
     setAlarmValue(ALARM_CODES::AIR_FAIL,        fast_data.pressure_air_regulated);
     setAlarmValue(ALARM_CODES::O2_FAIL,         fast_data.pressure_o2_regulated);
//     setAlarmValue(ALARM_CODES::CHECK_VALVE_EXHALE, );
//     setAlarmValue(ALARM_CODES::EXPIRATION_SENSE_FAULT_OR_LEAK, );
//     setAlarmValue(ALARM_CODES::EXPIRATION_VALVE_Leak, );
//     setAlarmValue(ALARM_CODES::INTENTIONAL_STOP, );
//     ALARM_CODES::LOW_BATTERY
//     setAlarmValue(ALARM_CODES::OCCLUSION, );
//     ALARM_CODES::AC_POWER_DISCONNECTION
//     ALARM_CODES::BATTERY_FAULT_SRVC
//     ALARM_CODES::BATTERY_CHARGE
//     setAlarmValue(ALARM_CODES::PRESSURE_SENSOR_FAULT, );
//     setAlarmValue(ALARM_CODES::ARDUINO_FAIL, );
}

void AlarmLoop::setBatteryThresholds()
{

    // these are booleans
    // 0 = no alarm
    // 1 = alarm
    // => limits are -1 to 0.5

    setAlarmThresholdMin(ALARM_CODES::AC_POWER_DISCONNECTION, -1.0);
    setAlarmThresholdMin(ALARM_CODES::BATTERY_CHARGE,         -1.0);
    setAlarmThresholdMin(ALARM_CODES::BATTERY_FAULT_SRVC,     -1.0);
    setAlarmThresholdMin(ALARM_CODES::LOW_BATTERY,            -1.0);

    setAlarmThresholdMax(ALARM_CODES::AC_POWER_DISCONNECTION,  0.5);
    setAlarmThresholdMax(ALARM_CODES::BATTERY_CHARGE,          0.5);
    setAlarmThresholdMax(ALARM_CODES::BATTERY_FAULT_SRVC,      0.5);
    setAlarmThresholdMax(ALARM_CODES::LOW_BATTERY,             0.5);
}

void AlarmLoop::setBatteryAlarms(battery_data_format &bat)
{
    bool ac_power_disconnection = (bat.ok == 0);
    bool battery_charge         = (bat.rdy2buf == 0);
    bool low_battery            = (bat.process_bat85 == 0);
    bool battery_fault_svc      = ((bat.prob_elec == 1) || (bat.alarm == 1));

    setAlarmValue(ALARM_CODES::AC_POWER_DISCONNECTION, ac_power_disconnection);
    setAlarmValue(ALARM_CODES::BATTERY_CHARGE,         battery_charge);
    setAlarmValue(ALARM_CODES::BATTERY_FAULT_SRVC,     battery_fault_svc);
    setAlarmValue(ALARM_CODES::LOW_BATTERY,            low_battery);
}

bool AlarmLoop::shouldPatientPressureAlarmTrigger(bool alarm_was_active) {
    // On low patient airway pressure, alarm should trigger regardless.
    if (_alarms.values[ALARM_CODES::CHECK_P_PATIENT] < _alarms.thresholds_min[ALARM_CODES::CHECK_P_PATIENT]) return true;

    // If high patient airway pressure, then alarm should only trigger if this pressure
    // persists for longer than COUGH_DELAY ms. This is a heuristic to avoid coughs
    // triggering alarm.

    uint32_t now = millis();

    static uint32_t last_trigger_time;

    if (!alarm_was_active) last_trigger_time = now;

    return (now >= last_trigger_time + COUGH_DELAY);
}

void AlarmLoop::handlePatientPressureAlarmActive(bool alarm_was_active) {
    // If the high pressure alarm has continued to remain active for defined period,
    // then we must force open the exhale valve.
    //      NOTE(Matthew): In the way alarms are handled by the HEV codebase, this doesn't
    //                     encode in-time data ideally. In principle a faulty pressure
    //                     sensor that sends high/low pressure signals, or a patient
    //                     whose airway pressure is building up and dropping for a short
    //                     time before buildup once more, could cause the outcome of
    //                     forcing the exhale valve open.

    uint32_t now = millis();

    static uint32_t last_trigger_time;

    if (!alarm_was_active) last_trigger_time = now;

    if (now >= last_trigger_time + HIGH_PAW_MAX_TIME_TIL_OPEN_VALVE) {
            //_breathing_loop->setForceExhale(true);
    }
}
