/**
 ******************************************************************************
 * @addtogroup OpenPilotSystem OpenPilot System
 * @{
 * @addtogroup OpenPilotLibraries OpenPilot System Libraries
 * @brief OpenPilot System libraries are available to all OP modules.
 * @{
 * @file       alarms.c
 * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
 * @brief      Library for setting and clearing system alarms
 * @see        The GNU Public License (GPL) Version 3
 *
 *****************************************************************************/
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include <openpilot.h>
#include <pios_struct_helper.h>
#include "inc/alarms.h"

// Private constants
#ifndef PIOS_ALARM_GRACETIME
// alarm cannot be turned off for at least 1000 milliseconds
// to prevent event system overload through flapping alarms
        #define PIOS_ALARM_GRACETIME 1000
#endif // PIOS_ALARM_GRACETIME

// Private types

// Private variables
static xSemaphoreHandle lock;
static volatile uint16_t lastAlarmChange[SYSTEMALARMS_ALARM_NUMELEM] = { 0 }; // this deliberately overflows every 2^16 milliseconds to save memory

// Private functions
static int32_t hasSeverity(SystemAlarmsAlarmOptions severity);

/**
 * Initialize the alarms library
 */
int32_t AlarmsInitialize(void)
{
    SystemAlarmsInitialize();

    lock = xSemaphoreCreateRecursiveMutex();
    // do not change the default states of the alarms, let the init code generated by the uavobjectgenerator handle that
    // AlarmsClearAll();
    // AlarmsDefaultAll();
    return 0;
}

/**
 * Set an alarm
 * @param alarm The system alarm to be modified
 * @param severity The alarm severity
 * @return 0 if success, -1 if an error
 */
int32_t AlarmsSet(SystemAlarmsAlarmElem alarm, SystemAlarmsAlarmOptions severity)
{
    SystemAlarmsAlarmData alarms;

    // Check that this is a valid alarm
    if (alarm >= SYSTEMALARMS_ALARM_NUMELEM) {
        return -1;
    }

    // Lock
    xSemaphoreTakeRecursive(lock, portMAX_DELAY);

    // Read alarm and update its severity only if it was changed
    SystemAlarmsAlarmGet(&alarms);
    uint16_t flightTime = (uint16_t)xTaskGetTickCount() * (uint16_t)portTICK_RATE_MS; // this deliberately overflows every 2^16 milliseconds to save memory
    if (((uint16_t)(flightTime - lastAlarmChange[alarm]) > PIOS_ALARM_GRACETIME &&
         cast_struct_to_array(alarms, alarms.Actuator)[alarm] != severity)
        || cast_struct_to_array(alarms, alarms.Actuator)[alarm] < severity) {
        cast_struct_to_array(alarms, alarms.Actuator)[alarm] = severity;
        lastAlarmChange[alarm] = flightTime;
        SystemAlarmsAlarmSet(&alarms);
    }

    // Release lock
    xSemaphoreGiveRecursive(lock);
    return 0;
}

/**
 * Set an Extended Alarm
 * @param alarm The system alarm to be modified
 * @param severity The alarm severity
 * @param status The Extended alarm status field
 * @param subStatus The Extended alarm substatus field
 * @return 0 if success, -1 if an error
 */
int32_t ExtendedAlarmsSet(SystemAlarmsAlarmElem alarm,
                          SystemAlarmsAlarmOptions severity,
                          SystemAlarmsExtendedAlarmStatusOptions status,
                          uint8_t subStatus)
{
    SystemAlarmsData alarms;

    // Check that this is a valid alarm
    if (alarm >= SYSTEMALARMS_EXTENDEDALARMSTATUS_NUMELEM) {
        return -1;
    }

    // Lock
    xSemaphoreTakeRecursive(lock, portMAX_DELAY);

    // Read alarm and update its severity only if it was changed
    SystemAlarmsGet(&alarms);
    uint16_t flightTime = (uint16_t)xTaskGetTickCount() * (uint16_t)portTICK_RATE_MS; // this deliberately overflows every 2^16 milliseconds to save memory
    if (((uint16_t)(flightTime - lastAlarmChange[alarm]) > PIOS_ALARM_GRACETIME &&
         cast_struct_to_array(alarms.Alarm, alarms.Alarm.Actuator)[alarm] != severity)
        || cast_struct_to_array(alarms.Alarm, alarms.Alarm.Actuator)[alarm] < severity) {
        cast_struct_to_array(alarms.ExtendedAlarmStatus, alarms.ExtendedAlarmStatus.BootFault)[alarm]    = status;
        cast_struct_to_array(alarms.ExtendedAlarmSubStatus, alarms.ExtendedAlarmStatus.BootFault)[alarm] = subStatus;
        cast_struct_to_array(alarms.Alarm, alarms.Alarm.Actuator)[alarm] = severity;
        lastAlarmChange[alarm] = flightTime;
        SystemAlarmsSet(&alarms);
    }

    // Release lock
    xSemaphoreGiveRecursive(lock);
    return 0;
}

/**
 * Get an alarm
 * @param alarm The system alarm to be read
 * @return Alarm severity
 */
SystemAlarmsAlarmOptions AlarmsGet(SystemAlarmsAlarmElem alarm)
{
    SystemAlarmsAlarmData alarms;

    // Check that this is a valid alarm
    if (alarm >= SYSTEMALARMS_ALARM_NUMELEM) {
        return 0;
    }

    // Read alarm
    SystemAlarmsAlarmGet(&alarms);
    return cast_struct_to_array(alarms, alarms.Actuator)[alarm];
}

/**
 * Set an alarm to it's default value
 * @param alarm The system alarm to be modified
 * @return 0 if success, -1 if an error
 */
int32_t AlarmsDefault(SystemAlarmsAlarmElem alarm)
{
    return AlarmsSet(alarm, SYSTEMALARMS_ALARM_DEFAULT);
}

/**
 * Default all alarms
 */
void AlarmsDefaultAll()
{
    for (uint32_t n = 0; n < SYSTEMALARMS_ALARM_NUMELEM; ++n) {
        AlarmsDefault(n);
    }
}

/**
 * Clear an alarm
 * @param alarm The system alarm to be modified
 * @return 0 if success, -1 if an error
 */
int32_t AlarmsClear(SystemAlarmsAlarmElem alarm)
{
    if (alarm < SYSTEMALARMS_EXTENDEDALARMSTATUS_NUMELEM) {
        return ExtendedAlarmsSet(alarm, SYSTEMALARMS_ALARM_OK, SYSTEMALARMS_EXTENDEDALARMSTATUS_NONE, 0);
    } else {
        return AlarmsSet(alarm, SYSTEMALARMS_ALARM_OK);
    }
}

/**
 * Clear all alarms
 */
void AlarmsClearAll()
{
    for (uint32_t n = 0; n < SYSTEMALARMS_ALARM_NUMELEM; ++n) {
        AlarmsClear(n);
    }
}

/**
 * Check if there are any alarms with the given or higher severity
 * @return 0 if no alarms are found, 1 if at least one alarm is found
 */
int32_t AlarmsHasWarnings()
{
    return hasSeverity(SYSTEMALARMS_ALARM_WARNING);
}

/**
 * Check if there are any alarms with error or higher severity
 * @return 0 if no alarms are found, 1 if at least one alarm is found
 */
int32_t AlarmsHasErrors()
{
    return hasSeverity(SYSTEMALARMS_ALARM_ERROR);
}


/**
 * Check if there are any alarms with critical or higher severity
 * @return 0 if no alarms are found, 1 if at least one alarm is found
 */
int32_t AlarmsHasCritical()
{
    return hasSeverity(SYSTEMALARMS_ALARM_CRITICAL);
}


/**
 * Check if there are any alarms with the given or higher severity
 * @return 0 if no alarms are found, 1 if at least one alarm is found
 */
static int32_t hasSeverity(SystemAlarmsAlarmOptions severity)
{
    SystemAlarmsAlarmData alarms;

    // Lock
    xSemaphoreTakeRecursive(lock, portMAX_DELAY);

    // Read alarms
    SystemAlarmsAlarmGet(&alarms);

    // Go through alarms and check if any are of the given severity or higher
    for (uint32_t n = 0; n < SYSTEMALARMS_ALARM_NUMELEM; ++n) {
        if (cast_struct_to_array(alarms, alarms.Actuator)[n] >= severity) {
            xSemaphoreGiveRecursive(lock);
            return 1;
        }
    }

    // If this point is reached then no alarms found
    xSemaphoreGiveRecursive(lock);
    return 0;
}
/**
 * Get the highest alarm severity
 * @return
 */
SystemAlarmsAlarmOptions AlarmsGetHighestSeverity()
{
    SystemAlarmsAlarmData alarmsData;
    SystemAlarmsAlarmOptions highest = SYSTEMALARMS_ALARM_UNINITIALISED;

    // Lock
    xSemaphoreTakeRecursive(lock, portMAX_DELAY);

    // Read alarms
    SystemAlarmsAlarmGet(&alarmsData);

    // Go through alarms and find the highest severity
    uint32_t n = 0;
    while (n < SYSTEMALARMS_ALARM_NUMELEM && highest != SYSTEMALARMS_ALARM_CRITICAL) {
        if (cast_struct_to_array(alarmsData, alarmsData.Actuator)[n] > highest) {
            highest = cast_struct_to_array(alarmsData, alarmsData.Actuator)[n];
        }
        n++;
    }

    xSemaphoreGiveRecursive(lock);
    return highest;
}

/**
 * @}
 * @}
 */