/**************************************************************************
 *
 * Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 *   Additional changes, Copyright (c) 2018 Ed Hague <edward@bac-test.com>
 *
 *   2018.06.17 -    Attempting to write to Object_Name returned
 *UNKNOWN_PROPERTY. Now returns WRITE_ACCESS_DENIED
 *
 *********************************************************************/

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "bacnet/basic/binding/address.h"
#include "bacnet/bacdef.h"
#include "bacnet/bacdcode.h"
#include "bacnet/bacenum.h"
#include "bacnet/bacapp.h"
#include "bacnet/basic/services.h"
#include "bacnet/config.h"
#include "bacnet/basic/object/device.h"
#include "bacnet/event.h"
#include "bacnet/basic/services.h"
#include "bacnet/basic/sys/debug.h"
#include "bacnet/basic/tsm/tsm.h"
#include "bacnet/wp.h"
#include "bacnet/basic/object/nc.h"
#include "bacnet/datalink/datalink.h"

#define PRINTF debug_perror

#ifndef MAX_NOTIFICATION_CLASSES
#define MAX_NOTIFICATION_CLASSES 2
#endif

#if defined(INTRINSIC_REPORTING)
static NOTIFICATION_CLASS_INFO NC_Info[MAX_NOTIFICATION_CLASSES];

/* These three arrays are used by the ReadPropertyMultiple handler */
static const int Notification_Properties_Required[] = { PROP_OBJECT_IDENTIFIER,
    PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_NOTIFICATION_CLASS, PROP_PRIORITY,
    PROP_ACK_REQUIRED, PROP_RECIPIENT_LIST, -1 };

static const int Notification_Properties_Optional[] = { PROP_DESCRIPTION, -1 };

static const int Notification_Properties_Proprietary[] = { -1 };

void Notification_Class_Property_Lists(
    const int **pRequired, const int **pOptional, const int **pProprietary)
{
    if (pRequired)
        *pRequired = Notification_Properties_Required;
    if (pOptional)
        *pOptional = Notification_Properties_Optional;
    if (pProprietary)
        *pProprietary = Notification_Properties_Proprietary;
    return;
}

void Notification_Class_Init(void)
{
    uint8_t NotifyIdx = 0;
    unsigned i;

    for (NotifyIdx = 0; NotifyIdx < MAX_NOTIFICATION_CLASSES; NotifyIdx++) {
        /* init with zeros */
        memset(&NC_Info[NotifyIdx], 0x00, sizeof(NOTIFICATION_CLASS_INFO));
        /* set the basic parameters */
        NC_Info[NotifyIdx].Ack_Required = 0;
        NC_Info[NotifyIdx].Priority[TRANSITION_TO_OFFNORMAL] =
            255; /* The lowest priority for Normal message. */
        NC_Info[NotifyIdx].Priority[TRANSITION_TO_FAULT] =
            255; /* The lowest priority for Normal message. */
        NC_Info[NotifyIdx].Priority[TRANSITION_TO_NORMAL] =
            255; /* PRINTF lowest priority for Normal message. */
        /* configure for every day, all day long */
        for (i = 0; i < MAX_BACNET_DAYS_OF_WEEK; i++) {
            NC_Info[NotifyIdx].Recipient_List->ValidDays |= (1 << i);
        }
        NC_Info[NotifyIdx].Recipient_List->FromTime.hour = 0;
        NC_Info[NotifyIdx].Recipient_List->FromTime.min = 0;
        NC_Info[NotifyIdx].Recipient_List->FromTime.sec = 0;
        NC_Info[NotifyIdx].Recipient_List->FromTime.hundredths = 0;
        NC_Info[NotifyIdx].Recipient_List->ToTime.hour = 23;
        NC_Info[NotifyIdx].Recipient_List->ToTime.min = 59;
        NC_Info[NotifyIdx].Recipient_List->ToTime.sec = 59;
        NC_Info[NotifyIdx].Recipient_List->ToTime.hundredths = 0;
        NC_Info[NotifyIdx].Recipient_List->Transitions =
            TRANSITION_TO_OFFNORMAL_MASKED | TRANSITION_TO_FAULT_MASKED |
            TRANSITION_TO_NORMAL_MASKED;
        NC_Info[NotifyIdx].Recipient_List->ConfirmedNotify = false;
        NC_Info[NotifyIdx].Recipient_List->ConfirmedNotify = false;
        NC_Info[NotifyIdx].Recipient_List->Recipient.RecipientType =
            RECIPIENT_TYPE_DEVICE;
        /* initialize to *wildcard* device instance - invalid! */
        NC_Info[NotifyIdx].Recipient_List->Recipient._.DeviceIdentifier =
            BACNET_MAX_INSTANCE;
    }

    return;
}

/* we simply have 0-n object instances.  Yours might be */
/* more complex, and then you need validate that the */
/* given instance exists */
bool Notification_Class_Valid_Instance(uint32_t object_instance)
{
    unsigned int index;

    index = Notification_Class_Instance_To_Index(object_instance);
    if (index < MAX_NOTIFICATION_CLASSES)
        return true;

    return false;
}

/* we simply have 0-n object instances.  Yours might be */
/* more complex, and then count how many you have */
unsigned Notification_Class_Count(void)
{
    return MAX_NOTIFICATION_CLASSES;
}

/* we simply have 0-n object instances.  Yours might be */
/* more complex, and then you need to return the instance */
/* that correlates to the correct index */
uint32_t Notification_Class_Index_To_Instance(unsigned index)
{
    return index;
}

/* we simply have 0-n object instances.  Yours might be */
/* more complex, and then you need to return the index */
/* that correlates to the correct instance number */
unsigned Notification_Class_Instance_To_Index(uint32_t object_instance)
{
    unsigned index = MAX_NOTIFICATION_CLASSES;

    if (object_instance < MAX_NOTIFICATION_CLASSES)
        index = object_instance;

    return index;
}

bool Notification_Class_Object_Name(
    uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
{
    static char text_string[32] = ""; /* okay for single thread */
    unsigned int index;
    bool status = false;

    index = Notification_Class_Instance_To_Index(object_instance);
    if (index < MAX_NOTIFICATION_CLASSES) {
        sprintf(text_string, "NOTIFICATION CLASS %lu", (unsigned long)index);
        status = characterstring_init_ansi(object_name, text_string);
    }

    return status;
}

int Notification_Class_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
{
    NOTIFICATION_CLASS_INFO *CurrentNotify;
    BACNET_CHARACTER_STRING char_string;
    BACNET_OCTET_STRING octet_string;
    BACNET_BIT_STRING bit_string;
    uint8_t *apdu = NULL;
    uint8_t u8Val;
    int idx;
    int apdu_len = 0; /* return value */

    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
        (rpdata->application_data_len == 0)) {
        return 0;
    }

    apdu = rpdata->application_data;
    CurrentNotify =
        &NC_Info[Notification_Class_Instance_To_Index(rpdata->object_instance)];

    switch (rpdata->object_property) {
        case PROP_OBJECT_IDENTIFIER:
            apdu_len = encode_application_object_id(
                &apdu[0], OBJECT_NOTIFICATION_CLASS, rpdata->object_instance);
            break;

        case PROP_OBJECT_NAME:
        case PROP_DESCRIPTION:
            Notification_Class_Object_Name(
                rpdata->object_instance, &char_string);
            apdu_len =
                encode_application_character_string(&apdu[0], &char_string);
            break;

        case PROP_OBJECT_TYPE:
            apdu_len = encode_application_enumerated(
                &apdu[0], OBJECT_NOTIFICATION_CLASS);
            break;

        case PROP_NOTIFICATION_CLASS:
            apdu_len +=
                encode_application_unsigned(&apdu[0], rpdata->object_instance);
            break;

        case PROP_PRIORITY:
            if (rpdata->array_index == 0)
                apdu_len += encode_application_unsigned(&apdu[0], 3);
            else {
                if (rpdata->array_index == BACNET_ARRAY_ALL) {
                    apdu_len += encode_application_unsigned(&apdu[apdu_len],
                        CurrentNotify->Priority[TRANSITION_TO_OFFNORMAL]);
                    apdu_len += encode_application_unsigned(&apdu[apdu_len],
                        CurrentNotify->Priority[TRANSITION_TO_FAULT]);
                    apdu_len += encode_application_unsigned(&apdu[apdu_len],
                        CurrentNotify->Priority[TRANSITION_TO_NORMAL]);
                } else if (rpdata->array_index <= MAX_BACNET_EVENT_TRANSITION) {
                    apdu_len += encode_application_unsigned(&apdu[apdu_len],
                        CurrentNotify->Priority[rpdata->array_index - 1]);
                } else {
                    rpdata->error_class = ERROR_CLASS_PROPERTY;
                    rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
                    apdu_len = -1;
                }
            }
            break;

        case PROP_ACK_REQUIRED:
            u8Val = CurrentNotify->Ack_Required;

            bitstring_init(&bit_string);
            bitstring_set_bit(&bit_string, TRANSITION_TO_OFFNORMAL,
                (u8Val & TRANSITION_TO_OFFNORMAL_MASKED) ? true : false);
            bitstring_set_bit(&bit_string, TRANSITION_TO_FAULT,
                (u8Val & TRANSITION_TO_FAULT_MASKED) ? true : false);
            bitstring_set_bit(&bit_string, TRANSITION_TO_NORMAL,
                (u8Val & TRANSITION_TO_NORMAL_MASKED) ? true : false);
            /* encode bitstring */
            apdu_len +=
                encode_application_bitstring(&apdu[apdu_len], &bit_string);
            break;

        case PROP_RECIPIENT_LIST:
            /* encode all entry of Recipient_List */
            for (idx = 0; idx < NC_MAX_RECIPIENTS; idx++) {
                BACNET_DESTINATION *RecipientEntry;
                int i = 0;

                /* get pointer of current element for Recipient_List  - easier
                 * for use */
                RecipientEntry = &CurrentNotify->Recipient_List[idx];
                if (RecipientEntry->Recipient.RecipientType !=
                    RECIPIENT_TYPE_NOTINITIALIZED) {
                    /* Valid Days - BACnetDaysOfWeek - [bitstring] monday-sunday
                     */
                    u8Val = 0x01;
                    bitstring_init(&bit_string);

                    for (i = 0; i < MAX_BACNET_DAYS_OF_WEEK; i++) {
                        if (RecipientEntry->ValidDays & u8Val)
                            bitstring_set_bit(&bit_string, i, true);
                        else
                            bitstring_set_bit(&bit_string, i, false);
                        u8Val <<= 1; /* next day */
                    }
                    apdu_len += encode_application_bitstring(
                        &apdu[apdu_len], &bit_string);

                    /* From Time */
                    apdu_len += encode_application_time(
                        &apdu[apdu_len], &RecipientEntry->FromTime);

                    /* To Time */
                    apdu_len += encode_application_time(
                        &apdu[apdu_len], &RecipientEntry->ToTime);

                    /*
                       BACnetRecipient ::= CHOICE {
                       device [0] BACnetObjectIdentifier,
                       address [1] BACnetAddress
                       } */

                    /* CHOICE - device [0] BACnetObjectIdentifier */
                    if (RecipientEntry->Recipient.RecipientType ==
                        RECIPIENT_TYPE_DEVICE) {
                        apdu_len += encode_context_object_id(&apdu[apdu_len], 0,
                            OBJECT_DEVICE,
                            RecipientEntry->Recipient._.DeviceIdentifier);
                    }
                    /* CHOICE - address [1] BACnetAddress */
                    else if (RecipientEntry->Recipient.RecipientType ==
                        RECIPIENT_TYPE_ADDRESS) {
                        /* opening tag 1 */
                        apdu_len += encode_opening_tag(&apdu[apdu_len], 1);
                        /* network-number Unsigned16, */
                        apdu_len += encode_application_unsigned(&apdu[apdu_len],
                            RecipientEntry->Recipient._.Address.net);

                        /* mac-address OCTET STRING */
                        if (RecipientEntry->Recipient._.Address.net) {
                            octetstring_init(&octet_string,
                                RecipientEntry->Recipient._.Address.adr,
                                RecipientEntry->Recipient._.Address.len);
                        } else {
                            octetstring_init(&octet_string,
                                RecipientEntry->Recipient._.Address.mac,
                                RecipientEntry->Recipient._.Address.mac_len);
                        }
                        apdu_len += encode_application_octet_string(
                            &apdu[apdu_len], &octet_string);

                        /* closing tag 1 */
                        apdu_len += encode_closing_tag(&apdu[apdu_len], 1);

                    } else {
                        ;
                    } /* shouldn't happen */

                    /* Process Identifier - Unsigned32 */
                    apdu_len += encode_application_unsigned(
                        &apdu[apdu_len], RecipientEntry->ProcessIdentifier);

                    /* Issue Confirmed Notifications - boolean */
                    apdu_len += encode_application_boolean(
                        &apdu[apdu_len], RecipientEntry->ConfirmedNotify);

                    /* Transitions - BACnet Event Transition Bits [bitstring] */
                    u8Val = RecipientEntry->Transitions;

                    bitstring_init(&bit_string);
                    bitstring_set_bit(&bit_string, TRANSITION_TO_OFFNORMAL,
                        (u8Val & TRANSITION_TO_OFFNORMAL_MASKED) ? true
                                                                 : false);
                    bitstring_set_bit(&bit_string, TRANSITION_TO_FAULT,
                        (u8Val & TRANSITION_TO_FAULT_MASKED) ? true : false);
                    bitstring_set_bit(&bit_string, TRANSITION_TO_NORMAL,
                        (u8Val & TRANSITION_TO_NORMAL_MASKED) ? true : false);

                    apdu_len += encode_application_bitstring(
                        &apdu[apdu_len], &bit_string);
                }
            }
            break;

        default:
            rpdata->error_class = ERROR_CLASS_PROPERTY;
            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
            apdu_len = -1;
            break;
    }

    /*  only array properties can have array options */
    if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY) &&
        (rpdata->array_index != BACNET_ARRAY_ALL)) {
        rpdata->error_class = ERROR_CLASS_PROPERTY;
        rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
        apdu_len = BACNET_STATUS_ERROR;
    }

    return apdu_len;
}

bool Notification_Class_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
{
    NOTIFICATION_CLASS_INFO *CurrentNotify;
    NOTIFICATION_CLASS_INFO TmpNotify;
    BACNET_APPLICATION_DATA_VALUE value;
    uint8_t TmpPriority[MAX_BACNET_EVENT_TRANSITION]; /* BACnetARRAY[3] of
                                                         Unsigned */
    bool status = false;
    int iOffset;
    uint8_t idx;
    int len = 0;

    CurrentNotify = &NC_Info[Notification_Class_Instance_To_Index(
        wp_data->object_instance)];

    /* decode some of the request */
    len = bacapp_decode_application_data(
        wp_data->application_data, wp_data->application_data_len, &value);
    if (len < 0) {
        /* error while decoding - a value larger than we can handle */
        wp_data->error_class = ERROR_CLASS_PROPERTY;
        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
        return false;
    }
    if ((wp_data->object_property != PROP_PRIORITY) &&
        (wp_data->array_index != BACNET_ARRAY_ALL)) {
        /*  only array properties can have array options */
        wp_data->error_class = ERROR_CLASS_PROPERTY;
        wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
        return false;
    }
    switch (wp_data->object_property) {
        case PROP_PRIORITY:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
            if (status) {
                if (wp_data->array_index == 0) {
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
                    status = false;
                } else if (wp_data->array_index == BACNET_ARRAY_ALL) {
                    iOffset = 0;
                    for (idx = 0; idx < MAX_BACNET_EVENT_TRANSITION; idx++) {
                        len = bacapp_decode_application_data(
                            &wp_data->application_data[iOffset],
                            wp_data->application_data_len, &value);
                        if ((len == 0) ||
                            (value.tag !=
                                BACNET_APPLICATION_TAG_UNSIGNED_INT)) {
                            /* Bad decode, wrong tag or following required
                             * parameter missing */
                            wp_data->error_class = ERROR_CLASS_PROPERTY;
                            wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                            status = false;
                            break;
                        }
                        if (value.type.Unsigned_Int > 255) {
                            wp_data->error_class = ERROR_CLASS_PROPERTY;
                            wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
                            status = false;
                            break;
                        }
                        TmpPriority[idx] = (uint8_t)value.type.Unsigned_Int;
                        iOffset += len;
                    }
                    if (status == true) {
                        for (idx = 0; idx < MAX_BACNET_EVENT_TRANSITION; idx++)
                            CurrentNotify->Priority[idx] = TmpPriority[idx];
                    }
                } else if (wp_data->array_index <= 3) {
                    if (value.type.Unsigned_Int > 255) {
                        wp_data->error_class = ERROR_CLASS_PROPERTY;
                        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
                        status = false;
                    } else
                        CurrentNotify->Priority[wp_data->array_index - 1] =
                            value.type.Unsigned_Int;
                } else {
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
                    status = false;
                }
            }
            break;

        case PROP_ACK_REQUIRED:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_BIT_STRING);
            if (status) {
                if (value.type.Bit_String.bits_used == 3) {
                    CurrentNotify->Ack_Required =
                        value.type.Bit_String.value[0];
                } else {
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
                }
            }
            break;

        case PROP_RECIPIENT_LIST:

            memset(&TmpNotify, 0x00, sizeof(NOTIFICATION_CLASS_INFO));
            idx = 0;
            iOffset = 0;
            /* decode all packed */
            while (iOffset < wp_data->application_data_len) {
                /* Decode Valid Days */
                len = bacapp_decode_application_data(
                    &wp_data->application_data[iOffset],
                    wp_data->application_data_len, &value);

                if ((len == 0) ||
                    (value.tag != BACNET_APPLICATION_TAG_BIT_STRING)) {
                    /* Bad decode, wrong tag or following required parameter
                     * missing */
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                    return false;
                }

                if (value.type.Bit_String.bits_used == MAX_BACNET_DAYS_OF_WEEK)
                    /* store value */
                    TmpNotify.Recipient_List[idx].ValidDays =
                        value.type.Bit_String.value[0];
                else {
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_OTHER;
                    return false;
                }

                iOffset += len;
                /* Decode From Time */
                len = bacapp_decode_application_data(
                    &wp_data->application_data[iOffset],
                    wp_data->application_data_len, &value);

                if ((len == 0) || (value.tag != BACNET_APPLICATION_TAG_TIME)) {
                    /* Bad decode, wrong tag or following required parameter
                     * missing */
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                    return false;
                }
                /* store value */
                TmpNotify.Recipient_List[idx].FromTime = value.type.Time;

                iOffset += len;
                /* Decode To Time */
                len = bacapp_decode_application_data(
                    &wp_data->application_data[iOffset],
                    wp_data->application_data_len, &value);

                if ((len == 0) || (value.tag != BACNET_APPLICATION_TAG_TIME)) {
                    /* Bad decode, wrong tag or following required parameter
                     * missing */
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                    return false;
                }
                /* store value */
                TmpNotify.Recipient_List[idx].ToTime = value.type.Time;

                iOffset += len;
                /* context tag [0] - Device */
                if (decode_is_context_tag(
                        &wp_data->application_data[iOffset], 0)) {
                    TmpNotify.Recipient_List[idx].Recipient.RecipientType =
                        RECIPIENT_TYPE_DEVICE;
                    /* Decode Network Number */
                    len = bacapp_decode_context_data(
                        &wp_data->application_data[iOffset],
                        wp_data->application_data_len, &value,
                        PROP_RECIPIENT_LIST);

                    if ((len == 0) ||
                        (value.tag != BACNET_APPLICATION_TAG_OBJECT_ID)) {
                        /* Bad decode, wrong tag or following required parameter
                         * missing */
                        wp_data->error_class = ERROR_CLASS_PROPERTY;
                        wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                        return false;
                    }
                    /* store value */
                    TmpNotify.Recipient_List[idx].Recipient._.DeviceIdentifier =
                        value.type.Object_Id.instance;

                    iOffset += len;
                }
                /* opening tag [1] - Recipient */
                else if (decode_is_opening_tag_number(
                             &wp_data->application_data[iOffset], 1)) {
                    iOffset++;
                    TmpNotify.Recipient_List[idx].Recipient.RecipientType =
                        RECIPIENT_TYPE_ADDRESS;
                    /* Decode Network Number */
                    len = bacapp_decode_application_data(
                        &wp_data->application_data[iOffset],
                        wp_data->application_data_len, &value);

                    if ((len == 0) ||
                        (value.tag != BACNET_APPLICATION_TAG_UNSIGNED_INT)) {
                        /* Bad decode, wrong tag or following required parameter
                         * missing */
                        wp_data->error_class = ERROR_CLASS_PROPERTY;
                        wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                        return false;
                    }
                    /* store value */
                    TmpNotify.Recipient_List[idx].Recipient._.Address.net =
                        value.type.Unsigned_Int;

                    iOffset += len;
                    /* Decode Address */
                    len = bacapp_decode_application_data(
                        &wp_data->application_data[iOffset],
                        wp_data->application_data_len, &value);

                    if ((len == 0) ||
                        (value.tag != BACNET_APPLICATION_TAG_OCTET_STRING)) {
                        /* Bad decode, wrong tag or following required parameter
                         * missing */
                        wp_data->error_class = ERROR_CLASS_PROPERTY;
                        wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                        return false;
                    }
                    /* store value */
                    if (TmpNotify.Recipient_List[idx].Recipient._.Address.net ==
                        0) {
                        memcpy(TmpNotify.Recipient_List[idx]
                                   .Recipient._.Address.mac,
                            value.type.Octet_String.value,
                            value.type.Octet_String.length);
                        TmpNotify.Recipient_List[idx]
                            .Recipient._.Address.mac_len =
                            value.type.Octet_String.length;
                    } else {
                        memcpy(TmpNotify.Recipient_List[idx]
                                   .Recipient._.Address.adr,
                            value.type.Octet_String.value,
                            value.type.Octet_String.length);
                        TmpNotify.Recipient_List[idx].Recipient._.Address.len =
                            value.type.Octet_String.length;
                    }

                    iOffset += len;
                    /* closing tag [1] - Recipient */
                    if (decode_is_closing_tag_number(
                            &wp_data->application_data[iOffset], 1))
                        iOffset++;
                    else {
                        /* Bad decode, wrong tag or following required parameter
                         * missing */
                        wp_data->error_class = ERROR_CLASS_PROPERTY;
                        wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                        return false;
                    }
                } else {
                    /* Bad decode, wrong tag or following required parameter
                     * missing */
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                    return false;
                }

                /* Process Identifier */
                len = bacapp_decode_application_data(
                    &wp_data->application_data[iOffset],
                    wp_data->application_data_len, &value);

                if ((len == 0) ||
                    (value.tag != BACNET_APPLICATION_TAG_UNSIGNED_INT)) {
                    /* Bad decode, wrong tag or following required parameter
                     * missing */
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                    return false;
                }
                /* store value */
                TmpNotify.Recipient_List[idx].ProcessIdentifier =
                    value.type.Unsigned_Int;

                iOffset += len;
                /* Issue Confirmed Notifications */
                len = bacapp_decode_application_data(
                    &wp_data->application_data[iOffset],
                    wp_data->application_data_len, &value);

                if ((len == 0) ||
                    (value.tag != BACNET_APPLICATION_TAG_BOOLEAN)) {
                    /* Bad decode, wrong tag or following required parameter
                     * missing */
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                    return false;
                }
                /* store value */
                TmpNotify.Recipient_List[idx].ConfirmedNotify =
                    value.type.Boolean;

                iOffset += len;
                /* Transitions */
                len = bacapp_decode_application_data(
                    &wp_data->application_data[iOffset],
                    wp_data->application_data_len, &value);

                if ((len == 0) ||
                    (value.tag != BACNET_APPLICATION_TAG_BIT_STRING)) {
                    /* Bad decode, wrong tag or following required parameter
                     * missing */
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
                    return false;
                }

                if (value.type.Bit_String.bits_used ==
                    MAX_BACNET_EVENT_TRANSITION)
                    /* store value */
                    TmpNotify.Recipient_List[idx].Transitions =
                        value.type.Bit_String.value[0];
                else {
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_OTHER;
                    return false;
                }
                iOffset += len;

                /* Increasing element of list */
                if (++idx >= NC_MAX_RECIPIENTS) {
                    wp_data->error_class = ERROR_CLASS_RESOURCES;
                    wp_data->error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY;
                    return false;
                }
            }

            /* Decoded all recipient list */
            /* copy elements from temporary object */
            for (idx = 0; idx < NC_MAX_RECIPIENTS; idx++) {
                BACNET_ADDRESS src = { 0 };
                unsigned max_apdu = 0;
                int32_t DeviceID;

                CurrentNotify->Recipient_List[idx] =
                    TmpNotify.Recipient_List[idx];

                if (CurrentNotify->Recipient_List[idx]
                        .Recipient.RecipientType == RECIPIENT_TYPE_DEVICE) {
                    /* copy Device_ID */
                    DeviceID = CurrentNotify->Recipient_List[idx]
                                   .Recipient._.DeviceIdentifier;
                    address_bind_request(DeviceID, &max_apdu, &src);

                } else if (CurrentNotify->Recipient_List[idx]
                               .Recipient.RecipientType ==
                    RECIPIENT_TYPE_ADDRESS) {
                    /* copy Address */
                    /* src =
                     * CurrentNotify->Recipient_List[idx].Recipient._.Address;
                     */
                    /* address_bind_request(BACNET_MAX_INSTANCE, &max_apdu,
                     * &src); */
                }
            }
            status = true;
            break;

        case PROP_OBJECT_NAME:
            wp_data->error_class = ERROR_CLASS_PROPERTY;
            wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
            break;

        default:
            wp_data->error_class = ERROR_CLASS_PROPERTY;
            wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
            break;
    }

    return status;
}

void Notification_Class_Get_Priorities(
    uint32_t Object_Instance, uint32_t *pPriorityArray)
{
    NOTIFICATION_CLASS_INFO *CurrentNotify;
    uint32_t object_index;
    int i;

    object_index = Notification_Class_Instance_To_Index(Object_Instance);

    if (object_index < MAX_NOTIFICATION_CLASSES)
        CurrentNotify = &NC_Info[object_index];
    else {
        for (i = 0; i < 3; i++)
            pPriorityArray[i] = 255;
        return; /* unknown object */
    }

    for (i = 0; i < 3; i++)
        pPriorityArray[i] = CurrentNotify->Priority[i];
}

static bool IsRecipientActive(
    BACNET_DESTINATION *pBacDest, uint8_t EventToState)
{
    BACNET_DATE_TIME DateTime;

    /* valid Transitions */
    switch (EventToState) {
        case EVENT_STATE_OFFNORMAL:
        case EVENT_STATE_HIGH_LIMIT:
        case EVENT_STATE_LOW_LIMIT:
            if (!(pBacDest->Transitions & TRANSITION_TO_OFFNORMAL_MASKED))
                return false;
            break;

        case EVENT_STATE_FAULT:
            if (!(pBacDest->Transitions & TRANSITION_TO_FAULT_MASKED))
                return false;
            break;

        case EVENT_STATE_NORMAL:
            if (!(pBacDest->Transitions & TRANSITION_TO_NORMAL_MASKED))
                return false;
            break;

        default:
            return false; /* shouldn't happen */
    }

    /* get actual date and time */
    Device_getCurrentDateTime(&DateTime);

    /* valid Days */
    if (!((0x01 << (DateTime.date.wday - 1)) & pBacDest->ValidDays))
        return false;

    /* valid FromTime */
    if (datetime_compare_time(&DateTime.time, &pBacDest->FromTime) < 0)
        return false;

    /* valid ToTime */
    if (datetime_compare_time(&pBacDest->ToTime, &DateTime.time) < 0)
        return false;

    return true;
}

void Notification_Class_common_reporting_function(
    BACNET_EVENT_NOTIFICATION_DATA *event_data)
{
    /* Fill the parameters common for all types of events. */

    NOTIFICATION_CLASS_INFO *CurrentNotify;
    BACNET_DESTINATION *pBacDest;
    uint32_t notify_index;
    uint8_t index;

    notify_index =
        Notification_Class_Instance_To_Index(event_data->notificationClass);

    if (notify_index < MAX_NOTIFICATION_CLASSES)
        CurrentNotify = &NC_Info[notify_index];
    else
        return;

    /* Initiating Device Identifier */
    event_data->initiatingObjectIdentifier.type = OBJECT_DEVICE;
    event_data->initiatingObjectIdentifier.instance =
        Device_Object_Instance_Number();

    /* Priority and AckRequired */
    switch (event_data->toState) {
        case EVENT_STATE_NORMAL:
            event_data->priority =
                CurrentNotify->Priority[TRANSITION_TO_NORMAL];
            event_data->ackRequired =
                (CurrentNotify->Ack_Required & TRANSITION_TO_NORMAL_MASKED)
                ? true
                : false;
            break;

        case EVENT_STATE_FAULT:
            event_data->priority = CurrentNotify->Priority[TRANSITION_TO_FAULT];
            event_data->ackRequired =
                (CurrentNotify->Ack_Required & TRANSITION_TO_FAULT_MASKED)
                ? true
                : false;
            break;

        case EVENT_STATE_OFFNORMAL:
        case EVENT_STATE_HIGH_LIMIT:
        case EVENT_STATE_LOW_LIMIT:
            event_data->priority =
                CurrentNotify->Priority[TRANSITION_TO_OFFNORMAL];
            event_data->ackRequired =
                (CurrentNotify->Ack_Required & TRANSITION_TO_OFFNORMAL_MASKED)
                ? true
                : false;
            break;

        default: /* shouldn't happen */
            break;
    }

    /* send notifications for active recipients */
    PRINTF("Notification Class[%u]: send notifications\n",
        event_data->notificationClass);
    /* pointer to first recipient */
    pBacDest = &CurrentNotify->Recipient_List[0];
    for (index = 0; index < NC_MAX_RECIPIENTS; index++, pBacDest++) {
        /* check if recipient is defined */
        if (pBacDest->Recipient.RecipientType == RECIPIENT_TYPE_NOTINITIALIZED)
            break; /* recipient doesn't defined - end of list */

        if (IsRecipientActive(pBacDest, event_data->toState)) {
            BACNET_ADDRESS dest;
            uint32_t device_id;
            unsigned max_apdu;

            /* Process Identifier */
            event_data->processIdentifier = pBacDest->ProcessIdentifier;

            /* send notification */
            if (pBacDest->Recipient.RecipientType == RECIPIENT_TYPE_DEVICE) {
                /* send notification to the specified device */
                device_id = pBacDest->Recipient._.DeviceIdentifier;
                PRINTF("Notification Class[%u]: send notification to %u\n",
                    event_data->notificationClass, (unsigned)device_id);
                if (pBacDest->ConfirmedNotify == true)
                    Send_CEvent_Notify(device_id, event_data);
                else if (address_get_by_device(device_id, &max_apdu, &dest))
                    Send_UEvent_Notify(
                        Handler_Transmit_Buffer, event_data, &dest);
            } else if (pBacDest->Recipient.RecipientType ==
                RECIPIENT_TYPE_ADDRESS) {
                PRINTF("Notification Class[%u]: send notification to ADDR\n",
                    event_data->notificationClass);
                /* send notification to the address indicated */
                if (pBacDest->ConfirmedNotify == true) {
                    if (address_get_device_id(&dest, &device_id))
                        Send_CEvent_Notify(device_id, event_data);
                } else {
                    dest = pBacDest->Recipient._.Address;
                    Send_UEvent_Notify(
                        Handler_Transmit_Buffer, event_data, &dest);
                }
            }
        }
    }
}

/* This function tries to find the addresses of the defined devices. */
/* It should be called periodically (example once per minute). */
void Notification_Class_find_recipient(void)
{
    NOTIFICATION_CLASS_INFO *CurrentNotify;
    BACNET_DESTINATION *pBacDest;
    BACNET_ADDRESS src = { 0 };
    unsigned max_apdu = 0;
    uint32_t notify_index;
    uint32_t DeviceID;
    uint8_t idx;

    for (notify_index = 0; notify_index < MAX_NOTIFICATION_CLASSES;
         notify_index++) {
        /* pointer to current notification */
        CurrentNotify =
            &NC_Info[Notification_Class_Instance_To_Index(notify_index)];
        /* pointer to first recipient */
        pBacDest = &CurrentNotify->Recipient_List[0];
        for (idx = 0; idx < NC_MAX_RECIPIENTS; idx++, pBacDest++) {
            if (CurrentNotify->Recipient_List[idx].Recipient.RecipientType ==
                RECIPIENT_TYPE_DEVICE) {
                /* Device ID */
                DeviceID = CurrentNotify->Recipient_List[idx]
                               .Recipient._.DeviceIdentifier;
                /* Send who_ is request only when address of device is unknown.
                 */
                if (DeviceID < BACNET_MAX_INSTANCE) {
                    /* note: BACNET_MAX_INSTANCE = wildcard, not valid */
                    if (!address_bind_request(DeviceID, &max_apdu, &src)) {
                        Send_WhoIs(DeviceID, DeviceID);
                    }
                }
            } else if (CurrentNotify->Recipient_List[idx]
                           .Recipient.RecipientType == RECIPIENT_TYPE_ADDRESS) {
            }
        }
    }
}
#endif /* defined(INTRINSIC_REPORTING) */
