/**
 * @file
 * @brief BACnetActionCommand codec used by Command objects
 * @author Steve Karg <skarg@users.sourceforge.net>
 * @date November 2024
 * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
 */
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/bacdcode.h"
#include "bacnet/bactimevalue.h"
#include "bacnet/timestamp.h"
/* me! */
#include "bacnet/bacaudit.h"

/**
 * @brief Encode the BACnetAuditValue
 * @param apdu - buffer of data to be encoded, or NULL for length
 * @param value - value to be encoded
 * @return the number of apdu bytes encoded
 */
int bacnet_audit_value_encode(uint8_t *apdu, const BACNET_AUDIT_VALUE *value)
{
    /* total length of the apdu, return value */
    int apdu_len = 0;

    if (!value) {
        return 0;
    }
    switch (value->tag) {
        case BACNET_APPLICATION_TAG_BOOLEAN:
            apdu_len =
                encode_application_boolean(apdu, value->type.boolean_value);
            break;
        case BACNET_APPLICATION_TAG_UNSIGNED_INT:
            apdu_len =
                encode_application_unsigned(apdu, value->type.unsigned_value);
            break;
        case BACNET_APPLICATION_TAG_SIGNED_INT:
            apdu_len =
                encode_application_signed(apdu, value->type.integer_value);
            break;
        case BACNET_APPLICATION_TAG_REAL:
            apdu_len = encode_application_real(apdu, value->type.real_value);
            break;
        case BACNET_APPLICATION_TAG_ENUMERATED:
            apdu_len = encode_application_enumerated(
                apdu, value->type.enumerated_value);
            break;
        case BACNET_APPLICATION_TAG_NULL:
        default:
            if (apdu) {
                apdu[0] = value->tag;
            }
            apdu_len++;
            break;
    }

    return apdu_len;
}

/**
 * @brief Encode the BACnetAuditValue
 * @param apdu - buffer of data to be encoded, or NULL for length
 * @param tag_number - context tag number to be encoded
 * @param value - value to be encoded
 * @return the number of apdu bytes encoded
 */
int bacnet_audit_value_context_encode(
    uint8_t *apdu, uint8_t tag_number, const BACNET_AUDIT_VALUE *value)
{
    int len;
    int apdu_len = 0;

    len = encode_opening_tag(apdu, tag_number);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    len = bacnet_audit_value_encode(apdu, value);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    len = encode_closing_tag(apdu, tag_number);
    apdu_len += len;

    return apdu_len;
}

/**
 * @brief Decode the BACnetAuditValue
 * @param apdu - buffer of data to be encoded, or NULL for length
 * @param tag_number - context tag number to be encoded
 * @param value - value to be encoded
 * @return the number of apdu bytes encoded, or BACNET_STATUS_ERROR if an error
 * occurs
 */
int bacnet_audit_value_decode(
    const uint8_t *apdu, uint32_t apdu_size, BACNET_AUDIT_VALUE *value)
{
    int len = 0;
    int apdu_len = 0;
    BACNET_TAG tag = { 0 };
    bool boolean_value = false;
    float real_value = 0.0f;
    uint32_t enumerated_value = 0;
    BACNET_UNSIGNED_INTEGER unsigned_value = 0;
    int32_t integer_value = 0;

    if (!value) {
        return BACNET_STATUS_ERROR;
    }
    if (!apdu) {
        return BACNET_STATUS_ERROR;
    }
    len = bacnet_tag_decode(apdu, apdu_size, &tag);
    if ((len > 0) && tag.application) {
        if (value) {
            value->tag = tag.number;
        }
        switch (tag.number) {
            case BACNET_APPLICATION_TAG_BOOLEAN:
                apdu_len = bacnet_boolean_application_decode(
                    apdu, apdu_size, &boolean_value);
                if (apdu_len > 0) {
                    if (value) {
                        value->type.boolean_value = boolean_value;
                    }
                }
                break;
            case BACNET_APPLICATION_TAG_UNSIGNED_INT:
                apdu_len = bacnet_unsigned_application_decode(
                    apdu, apdu_size, &unsigned_value);
                if (apdu_len > 0) {
                    if (value) {
                        value->type.unsigned_value = unsigned_value;
                    }
                }
                break;
            case BACNET_APPLICATION_TAG_SIGNED_INT:
                apdu_len = bacnet_signed_application_decode(
                    apdu, apdu_size, &integer_value);
                if (apdu_len > 0) {
                    if (value) {
                        value->type.integer_value = integer_value;
                    }
                }
                break;
            case BACNET_APPLICATION_TAG_REAL:
                apdu_len = bacnet_real_application_decode(
                    apdu, apdu_size, &real_value);
                if (apdu_len > 0) {
                    if (value) {
                        value->type.real_value = real_value;
                    }
                }
                break;
            case BACNET_APPLICATION_TAG_ENUMERATED:
                apdu_len = bacnet_enumerated_application_decode(
                    apdu, apdu_size, &enumerated_value);
                if (apdu_len > 0) {
                    if (value) {
                        value->type.enumerated_value = enumerated_value;
                    }
                }
                break;
            case BACNET_APPLICATION_TAG_NULL:
            default:
                apdu_len = len;
                break;
        }
    } else {
        apdu_len = BACNET_STATUS_ERROR;
    }

    return apdu_len;
}

/**
 * @brief Decode a context encoded BACnetAuditValue from a buffer
 * @param apdu - the APDU buffer
 * @param apdu_size - the size of the APDU buffer
 * @param tag_number - the tag number
 * @param value - BACnetAuditValue to decode into
 * @return number of bytes decoded, zero if tag mismatch, or
 * BACNET_STATUS_ERROR on failure.
 */
int bacnet_audit_value_context_decode(
    const uint8_t *apdu,
    uint16_t apdu_size,
    uint8_t tag_number,
    BACNET_AUDIT_VALUE *value)
{
    int len = 0;
    int apdu_len = 0;

    if (!apdu) {
        return BACNET_STATUS_ERROR;
    }
    if (!bacnet_is_opening_tag_number(
            &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) {
        return 0;
    }
    apdu_len += len;
    len =
        bacnet_audit_value_decode(&apdu[apdu_len], apdu_size - apdu_len, value);
    if (len > 0) {
        apdu_len += len;
        if (bacnet_is_closing_tag_number(
                &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) {
            apdu_len += len;
        } else {
            return BACNET_STATUS_ERROR;
        }
    } else {
        return BACNET_STATUS_ERROR;
    }

    return apdu_len;
}

/**
 * @brief Compare two BACnetActionPropertyValue complex datatypes
 * @param value1 [in] The first structure to compare
 * @param value2 [in] The second structure to compare
 * @return true if the two structures are the same
 */
bool bacnet_audit_value_same(
    const BACNET_AUDIT_VALUE *value1, const BACNET_AUDIT_VALUE *value2)
{
    bool status = false; /*return value */

    if ((value1 == NULL) || (value2 == NULL)) {
        return false;
    }
    if (value1->tag == value2->tag) {
        switch (value1->tag) {
            case BACNET_APPLICATION_TAG_NULL:
                status = true;
                break;
            case BACNET_APPLICATION_TAG_BOOLEAN:
                status =
                    value1->type.boolean_value == value2->type.boolean_value;
                break;
            case BACNET_APPLICATION_TAG_UNSIGNED_INT:
                status =
                    value1->type.unsigned_value == value2->type.unsigned_value;
                break;
            case BACNET_APPLICATION_TAG_SIGNED_INT:
                status =
                    value1->type.integer_value == value2->type.integer_value;
                break;
            case BACNET_APPLICATION_TAG_REAL:
                status = !islessgreater(
                    value1->type.real_value, value2->type.real_value);
                break;
            case BACNET_APPLICATION_TAG_ENUMERATED:
                status = value1->type.enumerated_value ==
                    value2->type.enumerated_value;
                break;
            default:
                break;
        }
    }

    return status;
}

/**
 * @brief Encode the BACnetAuditNotification
 * @param apdu - buffer of data to be encoded, or NULL for length
 * @param value - value to be encoded
 * @return the number of apdu bytes encoded
 */
int bacnet_audit_log_notification_encode(
    uint8_t *apdu, const BACNET_AUDIT_NOTIFICATION *value)
{
    int len, apdu_len = 0; /* total length of the apdu, return value */

    if (!value) {
        return 0;
    }
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_TIMESTAMP_ENABLE
    /* source-timestamp [0] BACnetTimeStamp OPTIONAL */
    len = bacapp_encode_context_timestamp(apdu, 0, &value->source_timestamp);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_TIMESTAMP_ENABLE
    /* target-timestamp [1] BACnetTimeStamp OPTIONAL */
    len = bacapp_encode_context_timestamp(apdu, 1, &value->target_timestamp);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
    /* source-device    [2] BACnetRecipient */
    len = bacnet_recipient_context_encode(apdu, 2, &value->source_device);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_OBJECT_ENABLE
    /* source-object    [3] BACnetObjectIdentifier OPTIONAL */
    len = encode_context_object_id(
        apdu, 3, value->source_object.type, value->source_object.instance);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
    /* operation        [4] BACnetAuditOperation */
    len = encode_context_unsigned(apdu, 4, value->operation);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_COMMENT_ENABLE
    /* source-comment   [5] CharacterString OPTIONAL */
    len = encode_context_character_string(apdu, 5, &value->source_comment);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_COMMENT_ENABLE
    /* target-comment   [6] CharacterString OPTIONAL */
    len = encode_context_character_string(apdu, 6, &value->target_comment);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_INVOKE_ID_ENABLE
    /* invoke-id        [7] Unsigned8 OPTIONAL */
    len = encode_context_unsigned(apdu, 7, value->invoke_id);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_USER_ID_ENABLE
    /* source-user-id   [8] Unsigned16 OPTIONAL */
    len = encode_context_unsigned(apdu, 8, value->source_user_id);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_USER_ROLE_ENABLE
    /* source-user-role [9] Unsigned8 OPTIONAL */
    len = encode_context_unsigned(apdu, 9, value->source_user_role);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
    /* target-device   [10] BACnetRecipient */
    len = bacnet_recipient_context_encode(apdu, 10, &value->target_device);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_OBJECT_ENABLE
    /* target-object   [11] BACnetObjectIdentifier OPTIONAL */
    len = encode_context_object_id(
        apdu, 11, value->target_object.type, value->target_object.instance);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_PROPERTY_ENABLE
    /* target-property [12] BACnetPropertyReference OPTIONAL */
    len = bacnet_property_reference_context_encode(
        apdu, 12, &value->target_property);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_PRIORITY_ENABLE
    /* target-priority [13] Unsigned (1..16) OPTIONAL */
    len = encode_context_unsigned(apdu, 13, value->target_priority);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_VALUE_ENABLE
    /* target-value    [14] ABSTRACT-SYNTAX.&Type OPTIONAL */
    len = bacnet_audit_value_context_encode(apdu, 14, &value->target_value);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_CURRENT_VALUE_ENABLE
    /* current-value   [15] ABSTRACT-SYNTAX.&Type OPTIONAL */
    len = bacnet_audit_value_context_encode(apdu, 15, &value->current_value);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_RESULT_ENABLE
    /* result          [16] Error OPTIONAL */
    len = encode_context_enumerated(apdu, 16, value->result);
    apdu_len += len;
#endif

    return apdu_len;
}

/**
 * @brief Decode the BACnetAuditNotification
 * @param apdu - buffer of data to be decoded
 * @param value - value to hold the decoded data
 * @return the number of apdu bytes encoded, or BACNET_STATUS_ERROR if an error
 * occurs
 */
int bacnet_audit_log_notification_decode(
    const uint8_t *apdu, uint32_t apdu_size, BACNET_AUDIT_NOTIFICATION *value)
{
    int len = 0;
    int apdu_len = 0;
    BACNET_UNSIGNED_INTEGER unsigned_value;
    BACNET_TIMESTAMP timestamp;
    BACNET_OBJECT_TYPE object_type;
    uint32_t object_instance;
    BACNET_CHARACTER_STRING char_string;
    BACNET_RECIPIENT recipient;
    struct BACnetPropertyReference property_reference;
    BACNET_AUDIT_VALUE audit_value;
    uint32_t enumerated_value;

    if (!apdu) {
        return BACNET_STATUS_ERROR;
    }
    /* source-timestamp [0] BACnetTimeStamp OPTIONAL */
    len = bacnet_timestamp_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 0, &timestamp);
    if (len > 0) {
        apdu_len += len;
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_TIMESTAMP_ENABLE
        if (value) {
            bacapp_timestamp_copy(&value->source_timestamp, &timestamp);
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* target-timestamp [1] BACnetTimeStamp OPTIONAL */
    len = bacnet_timestamp_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 1, &timestamp);
    if (len > 0) {
        apdu_len += len;
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_TIMESTAMP_ENABLE
        if (value) {
            bacapp_timestamp_copy(&value->target_timestamp, &timestamp);
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* source-device    [2] BACnetRecipient */
    len = bacnet_recipient_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 2, &recipient);
    if (len > 0) {
        apdu_len += len;
        if (value) {
            bacnet_recipient_copy(&value->source_device, &recipient);
        }
    } else {
        return BACNET_STATUS_ERROR;
    }
    /* source-object    [3] BACnetObjectIdentifier OPTIONAL */
    len = bacnet_object_id_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 3, &object_type,
        &object_instance);
    if (len > 0) {
        apdu_len += len;
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_OBJECT_ENABLE
        if (value) {
            value->source_object.type = object_type;
            value->source_object.instance = object_instance;
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* operation        [4] BACnetAuditOperation */
    len = bacnet_unsigned_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 4, &unsigned_value);
    if (len > 0) {
        if (unsigned_value < AUDIT_OPERATION_MAX) {
            if (value) {
                value->operation = (BACNET_AUDIT_OPERATION)unsigned_value;
            }
        } else {
            return BACNET_STATUS_ERROR;
        }
        apdu_len += len;
    } else {
        return BACNET_STATUS_ERROR;
    }
    /* source-comment   [5] CharacterString OPTIONAL */
    len = bacnet_character_string_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 5, &char_string);
    if (len > 0) {
        apdu_len += len;
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_COMMENT_ENABLE
        if (value) {
            characterstring_copy(&value->source_comment, &char_string);
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* target-comment   [6] CharacterString OPTIONAL */
    len = bacnet_character_string_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 6, &char_string);
    if (len > 0) {
        apdu_len += len;
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_COMMENT_ENABLE
        if (value) {
            characterstring_copy(&value->target_comment, &char_string);
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* invoke-id        [7] Unsigned8 OPTIONAL */
    len = bacnet_unsigned_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 7, &unsigned_value);
    if (len > 0) {
        apdu_len += len;
        if (unsigned_value > UINT8_MAX) {
            return BACNET_STATUS_ERROR;
        }
#ifdef BACNET_AUDIT_NOTIFICATION_INVOKE_ID_ENABLE
        if (value) {
            value->invoke_id = (uint8_t)unsigned_value;
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* source-user-id   [8] Unsigned16 OPTIONAL */
    if (bacnet_is_context_tag_number(
            &apdu[apdu_len], apdu_size - apdu_len, 8, NULL, NULL)) {
        len = bacnet_unsigned_context_decode(
            &apdu[apdu_len], apdu_size - apdu_len, 8, &unsigned_value);
        if (len > 0) {
            apdu_len += len;
            if (unsigned_value > UINT16_MAX) {
                return BACNET_STATUS_ERROR;
            }
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_USER_ID_ENABLE
            if (value) {
                value->source_user_id = (uint16_t)unsigned_value;
            }
#endif
        } else {
            return BACNET_STATUS_ERROR;
        }
    }
    /* source-user-role [9] Unsigned8 OPTIONAL */
    len = bacnet_unsigned_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 9, &unsigned_value);
    if (len > 0) {
        apdu_len += len;
        if (unsigned_value > UINT8_MAX) {
            return BACNET_STATUS_ERROR;
        }
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_USER_ROLE_ENABLE
        if (value) {
            value->source_user_role = (uint8_t)unsigned_value;
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* target-device   [10] BACnetRecipient */
    len = bacnet_recipient_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 10, &recipient);
    if (len > 0) {
        apdu_len += len;
        if (value) {
            bacnet_recipient_copy(&value->target_device, &recipient);
        }
    } else {
        return BACNET_STATUS_ERROR;
    }
    /* target-object   [11] BACnetObjectIdentifier OPTIONAL */
    len = bacnet_object_id_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 11, &object_type,
        &object_instance);
    if (len > 0) {
        apdu_len += len;
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_OBJECT_ENABLE
        if (value) {
            value->target_object.type = object_type;
            value->target_object.instance = object_instance;
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* target-property [12] BACnetPropertyReference OPTIONAL */
    len = bacnet_property_reference_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 12, &property_reference);
    if (len > 0) {
        apdu_len += len;
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_PROPERTY_ENABLE
        if (value) {
            memcpy(
                &value->target_property, &property_reference,
                sizeof(property_reference));
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* target-priority [13] Unsigned (1..16) OPTIONAL */
    len = bacnet_unsigned_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 13, &unsigned_value);
    if (len > 0) {
        apdu_len += len;
        if (unsigned_value > UINT8_MAX) {
            return BACNET_STATUS_ERROR;
        }
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_PRIORITY_ENABLE
        if (value) {
            value->target_priority = (uint8_t)unsigned_value;
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* target-value    [14] ABSTRACT-SYNTAX.&Type OPTIONAL */
    len = bacnet_audit_value_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 14, &audit_value);
    if (len > 0) {
        apdu_len += len;
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_VALUE_ENABLE
        if (value) {
            memcpy(&value->target_value, &audit_value, sizeof(audit_value));
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }
    /* current-value   [15] ABSTRACT-SYNTAX.&Type OPTIONAL */
    len = bacnet_audit_value_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 15, &audit_value);
    if (len > 0) {
        apdu_len += len;
#ifdef BACNET_AUDIT_NOTIFICATION_CURRENT_VALUE_ENABLE
        if (value) {
            memcpy(&value->current_value, &audit_value, sizeof(audit_value));
        }
#endif
    }
    /* result          [16] Error OPTIONAL */
    len = bacnet_enumerated_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 16, &enumerated_value);
    if (len > 0) {
        apdu_len += len;
        if (enumerated_value > UINT16_MAX) {
            return BACNET_STATUS_ERROR;
        }
#ifdef BACNET_AUDIT_NOTIFICATION_RESULT_ENABLE
        if (value) {
            value->result = (BACNET_ERROR_CODE)enumerated_value;
        }
#endif
    } else if (len < 0) {
        return BACNET_STATUS_ERROR;
    }

    return apdu_len;
}

/**
 * @brief Decode a context BACnetAuditLogNotification context data.
 * Check for an opening tag and a closing tag as well.
 *
 * @param apdu  Pointer to the buffer containing the encoded value
 * @param apdu_size Size of the buffer containing the encoded value
 * @param tag_number  Tag number
 * @param value  Pointer to the structure that shall be decoded into.
 *
 * @return number of bytes decoded, zero if tag mismatch,
 * or #BACNET_STATUS_ERROR (-1) if malformed
 */
int bacnet_audit_log_notification_context_decode(
    const uint8_t *apdu,
    uint16_t apdu_size,
    uint8_t tag_number,
    BACNET_AUDIT_NOTIFICATION *value)
{
    int len = 0;
    int apdu_len = 0;

    if (!apdu) {
        return BACNET_STATUS_ERROR;
    }
    if (!bacnet_is_opening_tag_number(
            &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) {
        return 0;
    }
    apdu_len += len;
    len = bacnet_audit_log_notification_decode(
        &apdu[apdu_len], apdu_size - apdu_len, value);
    if (len > 0) {
        apdu_len += len;
        if (bacnet_is_closing_tag_number(
                &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) {
            apdu_len += len;
        } else {
            return BACNET_STATUS_ERROR;
        }
    } else {
        return BACNET_STATUS_ERROR;
    }

    return apdu_len;
}

/**
 * @brief Compare two BACnetAuditNotification for same-ness
 * @param value1 - value 1 structure
 * @param value2 - value 2 structure
 * @return true if the values are the same
 */
bool bacnet_audit_log_notification_same(
    const BACNET_AUDIT_NOTIFICATION *value1,
    const BACNET_AUDIT_NOTIFICATION *value2)
{
    if (!value1 || !value2) {
        return false;
    }
    if (value1->operation != value2->operation) {
        return false;
    }
    if (!bacnet_recipient_same(
            &value1->source_device, &value2->source_device)) {
        return false;
    }
    if (!bacnet_recipient_same(
            &value1->target_device, &value2->target_device)) {
        return false;
    }
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_TIMESTAMP_ENABLE
    if (!bacapp_timestamp_same(
            &value1->source_timestamp, &value2->source_timestamp)) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_TIMESTAMP_ENABLE
    if (!bacapp_timestamp_same(
            &value1->target_timestamp, &value2->target_timestamp)) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_OBJECT_ENABLE
    if (!bacnet_object_id_same(
            value1->source_object.type, value1->source_object.instance,
            value2->source_object.type, value2->source_object.instance)) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_COMMENT_ENABLE
    if (!characterstring_same(
            &value1->source_comment, &value2->source_comment)) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_COMMENT_ENABLE
    if (!characterstring_same(
            &value1->target_comment, &value2->target_comment)) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_INVOKE_ID_ENABLE
    if (value1->invoke_id != value2->invoke_id) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_USER_ID_ENABLE
    if (value1->source_user_id != value2->source_user_id) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_SOURCE_USER_ROLE_ENABLE
    if (value1->source_user_role != value2->source_user_role) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_OBJECT_ENABLE
    if (!bacnet_object_id_same(
            value1->target_object.type, value1->target_object.instance,
            value2->target_object.type, value2->target_object.instance)) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_PROPERTY_ENABLE
    if (!bacnet_property_reference_same(
            &value1->target_property, &value2->target_property)) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_PRIORITY_ENABLE
    if (value1->target_priority != value2->target_priority) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_TARGET_VALUE_ENABLE
    if (!bacnet_audit_value_same(
            &value1->target_value, &value2->target_value)) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_CURRENT_VALUE_ENABLE
    if (!bacnet_audit_value_same(
            &value1->current_value, &value2->current_value)) {
        return false;
    }
#endif
#ifdef BACNET_AUDIT_NOTIFICATION_RESULT_ENABLE
    if (value1->result != value2->result) {
        return false;
    }
#endif

    return true;
}

/**
 * @brief Encode property value according to the application tag
 *
 * BACnetAuditLogRecord ::= SEQUENCE {
 *      timestamp [0] BACnetDateTime,
 *      log-datum [1] CHOICE {
 *          log-status [0] BACnetLogStatus,
 *          audit-notification [1] BACnetAuditNotification,
 *          time-change [2] REAL
 *      }
 * }
 *
 * @param apdu - Pointer to the buffer to encode to, or NULL for length
 * @param value - Pointer to the property value to encode from
 * @return number of bytes encoded
 */
int bacnet_audit_log_record_encode(
    uint8_t *apdu, const BACNET_AUDIT_LOG_RECORD *value)
{
    int len, apdu_len = 0; /* total length of the apdu, return value */
    BACNET_BIT_STRING log_status = { 0 };

    if (!value) {
        return 0;
    }
    /* timestamp [0] BACnetDateTime */
    len = bacapp_encode_context_datetime(apdu, 0, &value->timestamp);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    /* log-datum [1] CHOICE */
    len = encode_opening_tag(apdu, 1);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    switch (value->tag) {
        case AUDIT_LOG_DATUM_TAG_STATUS:
            /* log-status [0] BACnetLogStatus */
            bitstring_bits_used_set(&log_status, LOG_STATUS_MAX);
            bitstring_set_octet(&log_status, 0, value->log_datum.log_status);
            len = encode_context_bitstring(apdu, value->tag, &log_status);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            break;
        case AUDIT_LOG_DATUM_TAG_NOTIFICATION:
            /* audit-notification [1] BACnetAuditNotification */
            len = encode_opening_tag(apdu, value->tag);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = bacnet_audit_log_notification_encode(
                apdu, &value->log_datum.notification);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_closing_tag(apdu, value->tag);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            break;
        case AUDIT_LOG_DATUM_TAG_TIME_CHANGE:
            /* time-change [2] REAL */
            len = encode_context_real(
                apdu, value->tag, value->log_datum.time_change);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            break;
        default:
            break;
    }
    /* log-datum [1] CHOICE */
    len = encode_closing_tag(apdu, 1);
    apdu_len += len;

    return apdu_len;
}

/**
 * @brief Decode property value from the application buffer
 * @param apdu - Pointer to the buffer to decode from
 * @param apdu_size Size of the buffer to decode from
 * @param value - Pointer to the property value to decode to
 * @return number of bytes encoded, or BACNET_STATUS_ERROR if an error
 * occurs
 */
int bacnet_audit_log_record_decode(
    const uint8_t *apdu, uint32_t apdu_size, BACNET_AUDIT_LOG_RECORD *value)
{
    int len = 0;
    int apdu_len = 0;
    BACNET_TAG tag = { 0 };
    BACNET_DATE_TIME bdatetime = { 0 };
    BACNET_BIT_STRING log_status = { 0 };
    BACNET_AUDIT_NOTIFICATION notification = { 0 };
    float real_value = 0.0f;

    if (!apdu) {
        return BACNET_STATUS_ERROR;
    }
    /* timestamp [0] BACnetDateTime */
    len = bacnet_datetime_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 0, &bdatetime);
    if (len > 0) {
        if (value) {
            datetime_copy(&value->timestamp, &bdatetime);
        }
        apdu_len += len;
    } else {
        return BACNET_STATUS_ERROR;
    }
    /* log-datum [1] CHOICE */
    if (bacnet_is_opening_tag_number(
            &apdu[apdu_len], apdu_size - apdu_len, 1, &len)) {
        apdu_len += len;
    } else {
        return BACNET_STATUS_ERROR;
    }
    len = bacnet_tag_decode(&apdu[apdu_len], apdu_size - apdu_len, &tag);
    if (len <= 0) {
        return BACNET_STATUS_ERROR;
    }
    if (value) {
        value->tag = tag.number;
    }
    /* ignore the len. len is included in context decoder below. */
    switch (tag.number) {
        case AUDIT_LOG_DATUM_TAG_STATUS:
            /* log-status [0] BACnetLogStatus */
            len = bacnet_bitstring_context_decode(
                &apdu[apdu_len], apdu_size - apdu_len, tag.number, &log_status);
            if (len > 0) {
                if (value) {
                    value->log_datum.log_status =
                        bitstring_octet(&log_status, 0);
                }
                apdu_len += len;
            } else {
                return BACNET_STATUS_ERROR;
            }
            break;
        case AUDIT_LOG_DATUM_TAG_NOTIFICATION:
            /* audit-notification [1] BACnetAuditNotification */
            len = bacnet_audit_log_notification_context_decode(
                &apdu[apdu_len], apdu_size - apdu_len, tag.number,
                &notification);
            if (len > 0) {
                if (value) {
                    memmove(
                        &value->log_datum.notification, &notification,
                        sizeof(BACNET_AUDIT_NOTIFICATION));
                }
                apdu_len += len;
            } else {
                return BACNET_STATUS_ERROR;
            }
            break;
        case AUDIT_LOG_DATUM_TAG_TIME_CHANGE:
            /* time-change [2] REAL */
            len = bacnet_real_context_decode(
                &apdu[apdu_len], apdu_size - apdu_len, tag.number, &real_value);
            if (len > 0) {
                if (value) {
                    value->log_datum.time_change = real_value;
                }
                apdu_len += len;
            } else {
                return BACNET_STATUS_ERROR;
            }
            break;
        default:
            return BACNET_STATUS_ERROR;
    }
    /* log-datum [1] CHOICE */
    if (bacnet_is_closing_tag_number(
            &apdu[apdu_len], apdu_size - apdu_len, 1, &len)) {
        apdu_len += len;
    } else {
        return BACNET_STATUS_ERROR;
    }

    return apdu_len;
}

/**
 * @brief Compare two BACnetActionPropertyValue complex datatypes
 * @param value1 [in] The first structure to compare
 * @param value2 [in] The second structure to compare
 * @return true if the two structures are the same
 */
bool bacnet_audit_log_record_same(
    const BACNET_AUDIT_LOG_RECORD *value1,
    const BACNET_AUDIT_LOG_RECORD *value2)
{
    bool status = false; /*return value */

    if ((value1 == NULL) || (value2 == NULL)) {
        return false;
    }
    /* does the tag match? */
    if (value1->tag == value2->tag) {
        status = true;
    }
    if (status) {
        status = false;
        /* does the timestamp match? */
        if (datetime_compare(&value1->timestamp, &value2->timestamp) == 0) {
            status = true;
        }
    }
    if (status) {
        status = false;
        switch (value1->tag) {
            case AUDIT_LOG_DATUM_TAG_STATUS:
                if (value1->log_datum.log_status ==
                    value2->log_datum.log_status) {
                    status = true;
                }
                break;
            case AUDIT_LOG_DATUM_TAG_NOTIFICATION:
                if (bacnet_audit_log_notification_same(
                        &value1->log_datum.notification,
                        &value2->log_datum.notification)) {
                    status = true;
                }
                break;
            case AUDIT_LOG_DATUM_TAG_TIME_CHANGE:
                if (!islessgreater(
                        value1->log_datum.time_change,
                        value2->log_datum.time_change)) {
                    status = true;
                }
                break;
            default:
                status = false;
                break;
        }
    }

    return status;
}
