#include "Pufferfish/Application/States.h"

#include <utility>

#include "catch2/catch.hpp"

using Pufferfish::Application::ActiveLogEvents;
using Pufferfish::Application::AlarmLimits;
using Pufferfish::Application::NextLogEvents;
using Pufferfish::Application::Range;

SCENARIO(
    "The equality operator works correctly for simple POD structs from nanopb",
    "[Application::States]") {
  GIVEN("Range, a struct type generated by nanopb") {
    WHEN("the == operator is used to compare two instances whose fields are equal") {
      Range first{};
      first.lower = 1;
      first.upper = 2;
      Range second{};
      second.lower = 1;
      second.upper = 2;
      bool equal = (first == second);

      THEN("The operator returns true") { REQUIRE(equal); }
    }
  }
  WHEN("the == operator is used to compare two instances which differ only in the lower field") {
    Range first{};
    first.lower = 2;
    first.upper = 2;
    Range second{};
    second.lower = 1;
    second.upper = 2;
    bool equal = (first == second);

    THEN("The operator returns false") { REQUIRE(!equal); }
  }
  WHEN("the == operator is used to compare two instances which differ only in the upper field") {
    Range first{};
    first.lower = 1;
    first.upper = 2;
    Range second{};
    second.lower = 1;
    second.upper = 3;
    bool equal = (first == second);

    THEN("The operator returns false") { REQUIRE(!equal); }
  }
  WHEN("the == operator is used to compare two instances which differ in both fields") {
    Range first{};
    first.lower = 1;
    first.upper = 2;
    Range second{};
    second.lower = 3;
    second.upper = 4;
    bool equal = (first == second);

    THEN("The operator returns false") { REQUIRE(!equal); }
  }
}

std::pair<AlarmLimits, AlarmLimits> make_alarm_limits_test_pair() {
  AlarmLimits first{};
  first.time = 1234;
  first.fio2.lower = 1;
  first.fio2.upper = 2;
  first.has_fio2 = true;
  first.flow.lower = 3;
  first.flow.upper = 4;
  first.has_flow = true;
  AlarmLimits second{};
  second.time = 1234;
  second.fio2.lower = 1;
  second.fio2.upper = 2;
  second.has_fio2 = true;
  second.flow.lower = 3;
  second.flow.upper = 4;
  second.has_flow = true;
  return std::make_pair(first, second);
}

SCENARIO(
    "The equality operator works correctly for POD structs from nanopb with nested objects",
    "[Application::States]") {
  GIVEN("AlarmLimits, a struct type generated by nanopb") {
    WHEN(
        "the == operator is used to compare two instances whose fields are equal, and whose ranges "
        "are uninitialized") {
      AlarmLimits first{};
      first.time = 1234;
      AlarmLimits second{};
      second.time = 1234;
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
    WHEN(
        "the == operator is used to compare two instances whose fields are equal, and whose fio2 "
        "ranges are initialized") {
      AlarmLimits first{};
      first.time = 1234;
      first.fio2.lower = 1;
      first.fio2.upper = 2;
      first.has_fio2 = true;
      AlarmLimits second{};
      second.time = 1234;
      second.fio2.lower = 1;
      second.fio2.upper = 2;
      second.has_fio2 = true;
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
    WHEN(
        "the == operator is used to compare two instances whose fields are equal, and whose fio2 "
        "and flow ranges are initialized") {
      auto [first, second] = make_alarm_limits_test_pair();
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the lower field of "
        "the fio2 ranges, and whose flow ranges are initialized and equal") {
      auto [first, second] = make_alarm_limits_test_pair();
      first.fio2.lower = second.fio2.lower + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ in only the upper field of "
        "the flow ranges, and whose fio2 ranges are initialized and equal") {
      auto [first, second] = make_alarm_limits_test_pair();
      second.flow.upper = first.flow.upper + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }

    WHEN(
        "the == operator is used to compare two instances which differ in only a field of ranges "
        "whose 'has_(range)' fields are unset") {
      auto [first, second] = make_alarm_limits_test_pair();
      second.flow.upper = first.flow.upper + 100;
      first.has_flow = false;
      second.has_flow = false;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
  }
}

std::pair<NextLogEvents, NextLogEvents> make_next_log_events_test_pair() {
  NextLogEvents first{};
  first.next_expected = 1;
  first.total = 2;
  first.remaining = 3;
  first.session_id = 1234;
  first.elements[0].id = 4;
  first.elements[0].alarm_limits.lower = 5;
  first.elements[0].alarm_limits.upper = 6;
  first.elements[0].has_alarm_limits = false;
  first.elements_count = 1;
  NextLogEvents second{};
  second.next_expected = 1;
  second.total = 2;
  second.remaining = 3;
  second.session_id = 1234;
  second.elements[0].id = 4;
  second.elements[0].alarm_limits.lower = 5;
  second.elements[0].alarm_limits.upper = 6;
  second.elements[0].has_alarm_limits = false;
  second.elements_count = 1;
  return std::make_pair(first, second);
}

SCENARIO(
    "The custom-defined equality operators work correctly for POD structs from nanopb with arrays "
    "in them",
    "[Application::States]") {
  GIVEN("ActiveLogEvents, a struct type generated by nanopb which has a C-style array inside") {
    WHEN(
        "the == operator is used to compare two instances whose id arrays are equal, and whose "
        "id_count fields are equal") {
      ActiveLogEvents first{};
      first.id[0] = 1;
      first.id[1] = 2;
      first.id_count = 2;
      ActiveLogEvents second{};
      second.id[0] = 1;
      second.id[1] = 2;
      second.id_count = 2;
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
    WHEN(
        "the == operator is used to compare two instances whose id arrays differ only in the "
        "zeroth element, and whose id_count fields are both 1") {
      ActiveLogEvents first{};
      first.id[0] = 101;
      first.id_count = 1;
      ActiveLogEvents second{};
      second.id[0] = 1;
      second.id_count = 1;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }

    WHEN(
        "the == operator is used to compare two instances whose id arrays differ only in the first "
        "element, and whose id_count fields are both 1") {
      ActiveLogEvents first{};
      first.id[0] = 1;
      first.id[1] = 102;
      first.id_count = 1;
      ActiveLogEvents second{};
      second.id[0] = 1;
      second.id[1] = 2;
      second.id_count = 1;
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
  }
  GIVEN(
      "NextLogEvents, a struct type generated by nanopb which has a C-style array of LogEvent "
      "structs inside") {
    WHEN(
        "the == operator is used to compare two instances whose fields, including fields of "
        "elements in the array, are equal") {
      auto [first, second] = make_next_log_events_test_pair();
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the next_expected "
        "field") {
      auto [first, second] = make_next_log_events_test_pair();
      second.next_expected = first.next_expected + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the total "
        "field") {
      auto [first, second] = make_next_log_events_test_pair();
      first.total = second.total + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the remaining "
        "field") {
      auto [first, second] = make_next_log_events_test_pair();
      first.remaining = second.remaining + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the session_id "
        "field") {
      auto [first, second] = make_next_log_events_test_pair();
      first.session_id = second.session_id + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the zeroth element "
        "of the elements array, and whose elements_count fields are both 1") {
      auto [first, second] = make_next_log_events_test_pair();
      first.elements[0].alarm_limits.lower = second.elements[0].alarm_limits.lower + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }

    WHEN(
        "the == operator is used to compare two instances which differ only in the first element "
        "of the elements array, and whose elements_count fields are both 1") {
      auto [first, second] = make_next_log_events_test_pair();
      first.elements[1].alarm_limits.lower = second.elements[1].alarm_limits.lower + 100;
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
  }
}
/*
 * Copyright 2020, the Pez Globo team and the Pufferfish project contributors
 *
 * States.cpp
 *
 * Unit tests to confirm behavior of Application state-related units
 *
 */
#include "Pufferfish/Application/States.h"

#include <utility>

#include "catch2/catch.hpp"

using Pufferfish::Application::ActiveLogEvents;
using Pufferfish::Application::AlarmLimits;
using Pufferfish::Application::NextLogEvents;
using Pufferfish::Application::Range;

SCENARIO(
    "The equality operator works correctly for simple POD structs from nanopb",
    "[Application::States]") {
  GIVEN("Range, a struct type generated by nanopb") {
    WHEN("the == operator is used to compare two instances whose fields are equal") {
      Range first{};
      first.lower = 1;
      first.upper = 2;
      Range second{};
      second.lower = 1;
      second.upper = 2;
      bool equal = (first == second);

      THEN("The operator returns true") { REQUIRE(equal); }
    }
  }
  WHEN("the == operator is used to compare two instances which differ only in the lower field") {
    Range first{};
    first.lower = 2;
    first.upper = 2;
    Range second{};
    second.lower = 1;
    second.upper = 2;
    bool equal = (first == second);

    THEN("The operator returns false") { REQUIRE(!equal); }
  }
  WHEN("the == operator is used to compare two instances which differ only in the upper field") {
    Range first{};
    first.lower = 1;
    first.upper = 2;
    Range second{};
    second.lower = 1;
    second.upper = 3;
    bool equal = (first == second);

    THEN("The operator returns false") { REQUIRE(!equal); }
  }
  WHEN("the == operator is used to compare two instances which differ in both fields") {
    Range first{};
    first.lower = 1;
    first.upper = 2;
    Range second{};
    second.lower = 3;
    second.upper = 4;
    bool equal = (first == second);

    THEN("The operator returns false") { REQUIRE(!equal); }
  }
}

std::pair<AlarmLimits, AlarmLimits> make_alarm_limits_test_pair() {
  AlarmLimits first{};
  first.time = 1234;
  first.fio2.lower = 1;
  first.fio2.upper = 2;
  first.has_fio2 = true;
  first.flow.lower = 3;
  first.flow.upper = 4;
  first.has_flow = true;
  AlarmLimits second{};
  second.time = 1234;
  second.fio2.lower = 1;
  second.fio2.upper = 2;
  second.has_fio2 = true;
  second.flow.lower = 3;
  second.flow.upper = 4;
  second.has_flow = true;
  return std::make_pair(first, second);
}

SCENARIO(
    "The equality operator works correctly for POD structs from nanopb with nested objects",
    "[Application::States]") {
  GIVEN("AlarmLimits, a struct type generated by nanopb") {
    WHEN(
        "the == operator is used to compare two instances whose fields are equal, and whose ranges "
        "are uninitialized") {
      AlarmLimits first{};
      first.time = 1234;
      AlarmLimits second{};
      second.time = 1234;
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
    WHEN(
        "the == operator is used to compare two instances whose fields are equal, and whose fio2 "
        "ranges are initialized") {
      AlarmLimits first{};
      first.time = 1234;
      first.fio2.lower = 1;
      first.fio2.upper = 2;
      first.has_fio2 = true;
      AlarmLimits second{};
      second.time = 1234;
      second.fio2.lower = 1;
      second.fio2.upper = 2;
      second.has_fio2 = true;
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
    WHEN(
        "the == operator is used to compare two instances whose fields are equal, and whose fio2 "
        "and flow ranges are initialized") {
      auto [first, second] = make_alarm_limits_test_pair();
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the lower field of "
        "the fio2 ranges, and whose flow ranges are initialized and equal") {
      auto [first, second] = make_alarm_limits_test_pair();
      first.fio2.lower = second.fio2.lower + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ in only the upper field of "
        "the flow ranges, and whose fio2 ranges are initialized and equal") {
      auto [first, second] = make_alarm_limits_test_pair();
      second.flow.upper = first.flow.upper + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    // Note: the following is not intuitive behavior if we think about equality of protobufs rather
    // than equality of C++ structs, but it's what you'd expect to happen if you literally compared
    // each field in the struct. In order to achieve intuitive behavior, make sure to always set the
    // "has_(fieldname)" flag to true when you modify the field object at fieldname!
    WHEN(
        "the == operator is used to compare two instances which differ in only a field of ranges "
        "whose 'has_(range)' fields are unset") {
      auto [first, second] = make_alarm_limits_test_pair();
      second.flow.upper = first.flow.upper + 100;
      first.has_flow = false;
      second.has_flow = false;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
  }
}

std::pair<NextLogEvents, NextLogEvents> make_next_log_events_test_pair() {
  NextLogEvents first{};
  first.next_expected = 1;
  first.total = 2;
  first.remaining = 3;
  first.session_id = 1234;
  first.elements[0].id = 4;
  first.elements[0].alarm_limits.lower = 5;
  first.elements[0].alarm_limits.upper = 6;
  first.elements[0].has_alarm_limits = false;
  first.elements_count = 1;
  NextLogEvents second{};
  second.next_expected = 1;
  second.total = 2;
  second.remaining = 3;
  second.session_id = 1234;
  second.elements[0].id = 4;
  second.elements[0].alarm_limits.lower = 5;
  second.elements[0].alarm_limits.upper = 6;
  second.elements[0].has_alarm_limits = false;
  second.elements_count = 1;
  return std::make_pair(first, second);
}

SCENARIO(
    "The custom-defined equality operators work correctly for POD structs from nanopb with arrays "
    "in them",
    "[Application::States]") {
  GIVEN("ActiveLogEvents, a struct type generated by nanopb which has a C-style array inside") {
    WHEN(
        "the == operator is used to compare two instances whose id arrays are equal, and whose "
        "id_count fields are equal") {
      ActiveLogEvents first{};
      first.id[0] = 1;
      first.id[1] = 2;
      first.id_count = 2;
      ActiveLogEvents second{};
      second.id[0] = 1;
      second.id[1] = 2;
      second.id_count = 2;
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
    WHEN(
        "the == operator is used to compare two instances whose id arrays differ only in the "
        "zeroth element, and whose id_count fields are both 1") {
      ActiveLogEvents first{};
      first.id[0] = 101;
      first.id_count = 1;
      ActiveLogEvents second{};
      second.id[0] = 1;
      second.id_count = 1;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    // Note: the following is intuitive behavior if we think about equality of protobufs rather
    // than equality of C++ structs. This is because we can't rely on the Boost PFR library to
    // provide the equality operator, so we must implement our own equality operator, where we do
    // account for id_count
    WHEN(
        "the == operator is used to compare two instances whose id arrays differ only in the first "
        "element, and whose id_count fields are both 1") {
      ActiveLogEvents first{};
      first.id[0] = 1;
      first.id[1] = 102;
      first.id_count = 1;
      ActiveLogEvents second{};
      second.id[0] = 1;
      second.id[1] = 2;
      second.id_count = 1;
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
  }
  GIVEN(
      "NextLogEvents, a struct type generated by nanopb which has a C-style array of LogEvent "
      "structs inside") {
    WHEN(
        "the == operator is used to compare two instances whose fields, including fields of "
        "elements in the array, are equal") {
      auto [first, second] = make_next_log_events_test_pair();
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the next_expected "
        "field") {
      auto [first, second] = make_next_log_events_test_pair();
      second.next_expected = first.next_expected + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the total "
        "field") {
      auto [first, second] = make_next_log_events_test_pair();
      first.total = second.total + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the remaining "
        "field") {
      auto [first, second] = make_next_log_events_test_pair();
      first.remaining = second.remaining + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the session_id "
        "field") {
      auto [first, second] = make_next_log_events_test_pair();
      first.session_id = second.session_id + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    WHEN(
        "the == operator is used to compare two instances which differ only in the zeroth element "
        "of the elements array, and whose elements_count fields are both 1") {
      auto [first, second] = make_next_log_events_test_pair();
      first.elements[0].alarm_limits.lower = second.elements[0].alarm_limits.lower + 100;
      bool equal = (first == second);

      THEN("the operator returns false") { REQUIRE(!equal); }
    }
    // Note: the following is intuitive behavior if we think about equality of protobufs rather than
    // equality of C++ structs. This is because we can't rely on the Boost PFR library to provide
    // the equality operator, so we must implement our own equality operator, where we do account
    // for elements_count
    WHEN(
        "the == operator is used to compare two instances which differ only in the first element "
        "of the elements array, and whose elements_count fields are both 1") {
      auto [first, second] = make_next_log_events_test_pair();
      first.elements[1].alarm_limits.lower = second.elements[1].alarm_limits.lower + 100;
      bool equal = (first == second);

      THEN("the operator returns true") { REQUIRE(equal); }
    }
  }
}
