/*********************************************************************
 *        _       _         _
 *  _ __ | |_  _ | |  __ _ | |__   ___
 * | '__|| __|(_)| | / _` || '_ \ / __|
 * | |   | |_  _ | || (_| || |_) |\__ \
 * |_|    \__|(_)|_| \__,_||_.__/ |___/
 *
 * www.rt-labs.com
 * Copyright 2018 rt-labs AB, Sweden.
 *
 * This software is dual-licensed under GPLv3 and a commercial
 * license. See the file LICENSE.md distributed with this software for
 * full license information.
 ********************************************************************/

#include "utils_for_testing.h"
#include "mocks.h"

#include "pf_includes.h"

#include <gtest/gtest.h>

class AlarmTest : public PnetIntegrationTest
{
};

class AlarmUnitTest : public PnetUnitTest
{
};

TEST_F (AlarmUnitTest, AlarmCheckAddDiagItemToSummary)
{
   pnet_alarm_spec_t alarm_specifier;
   uint32_t maint_status = 0;
   pf_diag_item_t diag_item;
   pf_subslot_t subslot;
   pf_ar_t ar;

   /* Prepare default input data */
   memset (&ar, 0, sizeof (ar));
   memset (&subslot, 0, sizeof (subslot));
   subslot.p_ar = &ar;

   memset (&diag_item, 0, sizeof (diag_item));
   diag_item.usi = PF_USI_EXTENDED_CHANNEL_DIAGNOSIS;
   PF_DIAG_CH_PROP_SPEC_SET (
      diag_item.fmt.std.ch_properties,
      PF_DIAG_CH_PROP_SPEC_APPEARS);
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_FAULT);

   /* Happy case */
   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_TRUE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_TRUE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_TRUE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, 0UL);

   /* Other AR */
   subslot.p_ar = NULL;

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_TRUE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_TRUE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, 0UL);
   subslot.p_ar = &ar;

   /* Manufacturer specific (in USI format) */
   diag_item.usi = TEST_DIAG_USI_CUSTOM;

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_FALSE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_TRUE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_TRUE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_TRUE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, 0UL);
   diag_item.usi = PF_USI_EXTENDED_CHANNEL_DIAGNOSIS;

   /* Disappearing diagnosis */
   PF_DIAG_CH_PROP_SPEC_SET (
      diag_item.fmt.std.ch_properties,
      PF_DIAG_CH_PROP_SPEC_DISAPPEARS);

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_FALSE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, 0UL);
   PF_DIAG_CH_PROP_SPEC_SET (
      diag_item.fmt.std.ch_properties,
      PF_DIAG_CH_PROP_SPEC_APPEARS);

   /* Maintenance required */
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_REQUIRED);

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_TRUE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, BIT (0));
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_FAULT);

   /* Maintenance required, via qualified channel diagnosis (qual 7..16) */
   diag_item.usi = PF_USI_QUALIFIED_CHANNEL_DIAGNOSIS;
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_QUALIFIED);
   diag_item.fmt.std.qual_ch_qualifier = BIT (7);

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_TRUE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, (BIT (7) | BIT (0)));

   diag_item.fmt.std.qual_ch_qualifier = BIT (16);

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_TRUE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, (BIT (16) | BIT (0)));
   diag_item.usi = PF_USI_EXTENDED_CHANNEL_DIAGNOSIS;
   diag_item.fmt.std.qual_ch_qualifier = 0;
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_FAULT);

   /* Maintenance demanded */
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_DEMANDED);

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_TRUE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, BIT (1));
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_FAULT);

   /* Maintenance demanded, via qualified channel diagnosis (qual 17..26) */
   diag_item.usi = PF_USI_QUALIFIED_CHANNEL_DIAGNOSIS;
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_QUALIFIED);
   diag_item.fmt.std.qual_ch_qualifier = BIT (17);

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_TRUE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, (BIT (17) | BIT (1)));

   diag_item.fmt.std.qual_ch_qualifier = BIT (26);

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_TRUE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, (BIT (26) | BIT (1)));
   diag_item.usi = PF_USI_EXTENDED_CHANNEL_DIAGNOSIS;
   diag_item.fmt.std.qual_ch_qualifier = 0;
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_FAULT);

   /* Fault, via qualified channel diagnosis (qual 27..31) */
   diag_item.usi = PF_USI_QUALIFIED_CHANNEL_DIAGNOSIS;
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_QUALIFIED);
   diag_item.fmt.std.qual_ch_qualifier = BIT (27);

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_TRUE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_TRUE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_TRUE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, BIT (27));

   diag_item.fmt.std.qual_ch_qualifier = BIT (31);

   memset (&alarm_specifier, 0, sizeof (alarm_specifier));
   maint_status = 0;
   pf_alarm_add_diag_item_to_summary (
      &ar,
      &subslot,
      &diag_item,
      &alarm_specifier,
      &maint_status);
   EXPECT_TRUE ((bool)alarm_specifier.channel_diagnosis);
   EXPECT_FALSE ((bool)alarm_specifier.manufacturer_diagnosis);
   EXPECT_TRUE ((bool)alarm_specifier.submodule_diagnosis);
   EXPECT_TRUE ((bool)alarm_specifier.ar_diagnosis);
   EXPECT_EQ (maint_status, BIT (31));
   diag_item.usi = PF_USI_EXTENDED_CHANNEL_DIAGNOSIS;
   diag_item.fmt.std.qual_ch_qualifier = 0;
   PF_DIAG_CH_PROP_MAINT_SET (
      diag_item.fmt.std.ch_properties,
      PNET_DIAG_CH_PROP_MAINT_FAULT);
}

TEST_F (AlarmUnitTest, AlarmCheckSendQueueHandling)
{
   pf_alarm_send_queue_t queue;
   pf_alarm_data_t post_message;
   pf_alarm_data_t fetch_message;
   int ix = 0;
   int err = 0;
   const uint16_t SEQUENCE_START_NUMBER = 100;

   memset (&post_message, 0, sizeof (post_message));
   memset (&fetch_message, 0, sizeof (fetch_message));
   memset (&queue, 0, sizeof (queue));

   /* Set up queue */
   EXPECT_FALSE (pf_alarm_queue_is_available(&queue.accountant));
   pf_alarm_queue_mutex_create (&queue.accountant);
   pf_alarm_queue_mutex_create (&queue.accountant); /* No-op */
   EXPECT_TRUE (pf_alarm_queue_is_available(&queue.accountant));
   pf_alarm_send_queue_reset (&queue);
   EXPECT_EQ (queue.accountant.count, 0); /* Do not bother with mutex
                                            (this test runs in single thread) */

   /* Fill the queue */
   for (ix = 0; ix < PNET_MAX_ALARMS; ix++)
   {
      post_message.sequence_number = SEQUENCE_START_NUMBER + ix;
      err = pf_alarm_send_queue_post (&queue, &post_message);
      EXPECT_EQ (err, 0);
   }
   EXPECT_EQ (queue.accountant.count, PNET_MAX_ALARMS);

   /* Add to full queue */
   post_message.sequence_number += 1;
   err = pf_alarm_send_queue_post (&queue, &post_message);
   EXPECT_EQ (err, -1);
   EXPECT_EQ (queue.accountant.count, PNET_MAX_ALARMS);

   /* Fetch from the queue */
   for (ix = 0; ix < PNET_MAX_ALARMS; ix++)
   {
      err = pf_alarm_send_queue_fetch (&queue, &fetch_message);
      EXPECT_EQ (err, 0);
      EXPECT_EQ (fetch_message.sequence_number, SEQUENCE_START_NUMBER + ix);
   }
   EXPECT_EQ (queue.accountant.count, 0);

   /* Fetch from empty queue */
   err = pf_alarm_send_queue_fetch (&queue, &fetch_message);
   EXPECT_EQ (err, -1);
   EXPECT_EQ (queue.accountant.count, 0);

   /* Reset the queue */
   err = pf_alarm_send_queue_post (&queue, &post_message);
   EXPECT_EQ (err, 0);
   EXPECT_EQ (queue.accountant.count, 1);

   pf_alarm_send_queue_reset (&queue);
   EXPECT_EQ (queue.accountant.count, 0);

   /* Wrap read_index and write_index, by adding and fetching a lot */
   for (ix = 0; ix < PNET_MAX_ALARMS * 5; ix++)
   {
      post_message.sequence_number = SEQUENCE_START_NUMBER + ix;
      err = pf_alarm_send_queue_post (&queue, &post_message);
      EXPECT_EQ (err, 0);
      EXPECT_EQ (queue.accountant.count, 1);

      err = pf_alarm_send_queue_fetch (&queue, &fetch_message);
      EXPECT_EQ (err, 0);
      EXPECT_EQ (queue.accountant.count, 0);
      EXPECT_EQ (fetch_message.sequence_number, SEQUENCE_START_NUMBER + ix);
   }

   /* Close down queue */
   pf_alarm_send_queue_reset (&queue);
   EXPECT_EQ (queue.accountant.count, 0);
   EXPECT_TRUE (pf_alarm_queue_is_available(&queue.accountant));
   pf_alarm_queue_mutex_destroy (&queue.accountant);
   pf_alarm_queue_mutex_destroy (&queue.accountant); /* Should be safe */
   EXPECT_FALSE (pf_alarm_queue_is_available(&queue.accountant));

   /* Post to closed queue */
   post_message.sequence_number = SEQUENCE_START_NUMBER;
   err = pf_alarm_send_queue_post (&queue, &post_message);
   EXPECT_EQ (err, -1);

   /* Fetch from closed queue */
   err = pf_alarm_send_queue_fetch (&queue, &fetch_message);
   EXPECT_EQ (err, -1);
}

TEST_F (AlarmUnitTest, AlarmCheckReceiveQueueHandling)
{
   pf_alarm_receive_queue_t queue;
   pf_apmr_msg_t post_frame;
   pf_apmr_msg_t fetch_frame;
   int ix = 0;
   int err = 0;
   const uint16_t POSITION_START_NUMBER = 100;
   const uintptr_t POINTER_START_NUMBER = 1000;

   memset (&post_frame, 0, sizeof (post_frame));
   memset (&fetch_frame, 0, sizeof (fetch_frame));
   memset (&queue, 0, sizeof (queue));

   /* Set up queue */
   EXPECT_FALSE (pf_alarm_queue_is_available(&queue.accountant));
   pf_alarm_queue_mutex_create (&queue.accountant);
   pf_alarm_queue_mutex_create (&queue.accountant); /* No-op */
   EXPECT_TRUE (pf_alarm_queue_is_available(&queue.accountant));
   pf_alarm_receive_queue_reset (&queue);
   EXPECT_EQ (queue.accountant.count, 0); /* Do not bother with mutex
                                            (this test runs in single thread) */

   /* Fill the queue */
   for (ix = 0; ix < PNET_MAX_ALARMS; ix++)
   {
      post_frame.frame_id_pos = POSITION_START_NUMBER + ix;
      post_frame.p_buf = (pnal_buf_t *)(POINTER_START_NUMBER + ix);
      err = pf_alarm_receive_queue_post (&queue, &post_frame);
      EXPECT_EQ (err, 0);
      EXPECT_EQ (queue.accountant.count, ix + 1);
   }
   EXPECT_EQ (queue.accountant.count, PNET_MAX_ALARMS);

   /* Add to full queue */
   post_frame.frame_id_pos += 1;
   post_frame.p_buf -= 1;
   err = pf_alarm_receive_queue_post (&queue, &post_frame);
   EXPECT_EQ (err, -1);
   EXPECT_EQ (queue.accountant.count, PNET_MAX_ALARMS);

   /* Fetch from the queue */
   for (ix = 0; ix < PNET_MAX_ALARMS; ix++)
   {
      err = pf_alarm_receive_queue_fetch (&queue, &fetch_frame);
      EXPECT_EQ (err, 0);
      EXPECT_EQ (queue.accountant.count, PNET_MAX_ALARMS - ix - 1);
      EXPECT_EQ (fetch_frame.frame_id_pos, POSITION_START_NUMBER + ix);
      EXPECT_EQ ((uintptr_t)fetch_frame.p_buf, POINTER_START_NUMBER + ix);
   }
   EXPECT_EQ (queue.accountant.count, 0);

   /* Fetch from empty queue */
   err = pf_alarm_receive_queue_fetch (&queue, &fetch_frame);
   EXPECT_EQ (err, -1);
   EXPECT_EQ (queue.accountant.count, 0);

   /* Reset the queue (will free allocated buffers) */
   post_frame.frame_id_pos = 42;
   post_frame.p_buf = pnal_buf_alloc (PNAL_BUF_MAX_SIZE);
   err = pf_alarm_receive_queue_post (&queue, &post_frame);
   EXPECT_EQ (err, 0);
   EXPECT_EQ (queue.accountant.count, 1);

   pf_alarm_receive_queue_reset (&queue);
   EXPECT_EQ (queue.accountant.count, 0);

   /* Wrap read_index and write_index, by adding and fetching a lot */
   for (ix = 0; ix < PNET_MAX_ALARMS * 5; ix++)
   {
      post_frame.frame_id_pos = POSITION_START_NUMBER + ix;
      post_frame.p_buf = (pnal_buf_t *)(POINTER_START_NUMBER + ix);
      err = pf_alarm_receive_queue_post (&queue, &post_frame);
      EXPECT_EQ (err, 0);
      EXPECT_EQ (queue.accountant.count, 1);

      err = pf_alarm_receive_queue_fetch (&queue, &fetch_frame);
      EXPECT_EQ (err, 0);
      EXPECT_EQ (queue.accountant.count, 0);
      EXPECT_EQ (fetch_frame.frame_id_pos, POSITION_START_NUMBER + ix);
      EXPECT_EQ ((uintptr_t)fetch_frame.p_buf, POINTER_START_NUMBER + ix);
   }

   /* Close down queue */
   pf_alarm_receive_queue_reset (&queue);
   EXPECT_EQ (queue.accountant.count, 0);
   EXPECT_TRUE (pf_alarm_queue_is_available(&queue.accountant));
   pf_alarm_queue_mutex_destroy (&queue.accountant);
   pf_alarm_queue_mutex_destroy (&queue.accountant); /* Should be safe */
   EXPECT_FALSE (pf_alarm_queue_is_available(&queue.accountant));

   /* Post to closed queue */
   post_frame.frame_id_pos = POSITION_START_NUMBER;
   post_frame.p_buf = (pnal_buf_t *)(POINTER_START_NUMBER);
   err = pf_alarm_receive_queue_post (&queue, &post_frame);
   EXPECT_EQ (err, -1);

   /* Fetch from closed queue */
   err = pf_alarm_receive_queue_fetch (&queue, &fetch_frame);
   EXPECT_EQ (err, -1);
}
