// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.

#define CAF_SUITE binary_serializer

#include "caf/binary_serializer.hpp"

#include "core-test.hpp"
#include "nasty.hpp"

#include <cstring>
#include <vector>

#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/timestamp.hpp"

using namespace caf;

namespace {

byte operator"" _b(unsigned long long int x) {
  return static_cast<byte>(x);
}

byte operator"" _b(char x) {
  return static_cast<byte>(x);
}

struct arr {
  int8_t xs[3];
};

template <class Inspector>
bool inspect(Inspector& f, arr& x) {
  return f.object(x).fields(f.field("xs", x.xs));
}

struct fixture {
  template <class... Ts>
  auto save(const Ts&... xs) {
    byte_buffer result;
    binary_serializer sink{nullptr, result};
    if (!(sink.apply(xs) && ...))
      CAF_FAIL("binary_serializer failed to save: " << sink.get_error());
    return result;
  }

  template <class... Ts>
  void save_to_buf(byte_buffer& data, const Ts&... xs) {
    binary_serializer sink{nullptr, data};
    if (!(sink.apply(xs) && ...))
      CAF_FAIL("binary_serializer failed to save: " << sink.get_error());
  }
};

} // namespace

CAF_TEST_FIXTURE_SCOPE(binary_serializer_tests, fixture)

#define SUBTEST(msg)                                                           \
  CAF_MESSAGE(msg);                                                            \
  for (int subtest_dummy = 0; subtest_dummy < 1; ++subtest_dummy)

#define CHECK_SAVE(type, value, ...)                                           \
  CAF_CHECK_EQUAL(save(type{value}), byte_buffer({__VA_ARGS__}))

CAF_TEST(primitive types) {
  SUBTEST("8-bit integers") {
    CHECK_SAVE(int8_t, 60, 0b00111100_b);
    CHECK_SAVE(int8_t, -61, 0b11000011_b);
    CHECK_SAVE(uint8_t, 60u, 0b00111100_b);
    CHECK_SAVE(uint8_t, 195u, 0b11000011_b);
  }
  SUBTEST("16-bit integers") {
    CHECK_SAVE(int16_t, 85, 0b00000000_b, 0b01010101_b);
    CHECK_SAVE(int16_t, -32683, 0b10000000_b, 0b01010101_b);
    CHECK_SAVE(uint16_t, 85u, 0b00000000_b, 0b01010101_b);
    CHECK_SAVE(uint16_t, 32853u, 0b10000000_b, 0b01010101_b);
  }
  SUBTEST("32-bit integers") {
    CHECK_SAVE(int32_t, -345, 0xFF_b, 0xFF_b, 0xFE_b, 0xA7_b);
    CHECK_SAVE(uint32_t, 4294966951u, 0xFF_b, 0xFF_b, 0xFE_b, 0xA7_b);
  }
  SUBTEST("64-bit integers") {
    CHECK_SAVE(int64_t, -1234567890123456789ll, //
               0xEE_b, 0xDD_b, 0xEF_b, 0x0B_b, 0x82_b, 0x16_b, 0x7E_b, 0xEB_b);
    CHECK_SAVE(uint64_t, 17212176183586094827llu, //
               0xEE_b, 0xDD_b, 0xEF_b, 0x0B_b, 0x82_b, 0x16_b, 0x7E_b, 0xEB_b);
  }
  SUBTEST("floating points use IEEE-754 conversion") {
    CHECK_SAVE(float, 3.45f, 0x40_b, 0x5C_b, 0xCC_b, 0xCD_b);
  }
  SUBTEST("strings use a varbyte-encoded size prefix") {
    CHECK_SAVE(std::string, "hello", 5_b, 'h'_b, 'e'_b, 'l'_b, 'l'_b, 'o'_b);
  }
  SUBTEST("enum types") {
    CHECK_SAVE(test_enum, test_enum::a, 0_b, 0_b, 0_b, 0_b);
    CHECK_SAVE(test_enum, test_enum::b, 0_b, 0_b, 0_b, 1_b);
    CHECK_SAVE(test_enum, test_enum::c, 0_b, 0_b, 0_b, 2_b);
  }
}

CAF_TEST(concatenation) {
  SUBTEST("calling f(a, b) writes a and b into the buffer in order") {
    CAF_CHECK_EQUAL(save(int8_t{7}, int16_t{-32683}),
                    byte_buffer({7_b, 0x80_b, 0x55_b}));
    CAF_CHECK_EQUAL(save(int16_t{-32683}, int8_t{7}),
                    byte_buffer({0x80_b, 0x55_b, 7_b}));
  }
  SUBTEST("calling f(a) and then f(b) is equal to calling f(a, b)") {
    byte_buffer data;
    binary_serializer sink{nullptr, data};
    save_to_buf(data, int8_t{7});
    save_to_buf(data, int16_t{-32683});
    CAF_CHECK_EQUAL(data, byte_buffer({7_b, 0x80_b, 0x55_b}));
  }
  SUBTEST("calling f(make_pair(a, b)) is equal to calling f(a, b)") {
    using i8i16_pair = std::pair<int8_t, int16_t>;
    using i16i8_pair = std::pair<int16_t, int8_t>;
    CHECK_SAVE(i8i16_pair, std::make_pair(int8_t{7}, int16_t{-32683}), //
               7_b, 0x80_b, 0x55_b);
    CHECK_SAVE(i16i8_pair, std::make_pair(int16_t{-32683}, int8_t{7}), //
               0x80_b, 0x55_b, 7_b);
  }
  SUBTEST("calling f(make_tuple(a, b)) is equivalent to f(make_pair(a, b))") {
    using i8i16_tuple = std::tuple<int8_t, int16_t>;
    using i16i8_tuple = std::tuple<int16_t, int8_t>;
    CHECK_SAVE(i8i16_tuple, std::make_tuple(int8_t{7}, int16_t{-32683}), //
               7_b, 0x80_b, 0x55_b);
    CHECK_SAVE(i16i8_tuple, std::make_tuple(int16_t{-32683}, int8_t{7}), //
               0x80_b, 0x55_b, 7_b);
  }
  SUBTEST("arrays behave like tuples") {
    arr xs{{1, 2, 3}};
    CAF_CHECK_EQUAL(save(xs), byte_buffer({1_b, 2_b, 3_b}));
  }
}

CAF_TEST(container types) {
  SUBTEST("STL vectors") {
    CHECK_SAVE(std::vector<int8_t>, std::vector<int8_t>({1, 2, 4, 8}), //
               4_b, 1_b, 2_b, 4_b, 8_b);
  }
  SUBTEST("STL sets") {
    CHECK_SAVE(std::set<int8_t>, std::set<int8_t>({1, 2, 4, 8}), //
               4_b, 1_b, 2_b, 4_b, 8_b);
  }
}

CAF_TEST(binary serializer picks up inspect functions) {
  SUBTEST("node ID") {
    auto nid = make_node_id(123, "000102030405060708090A0B0C0D0E0F10111213");
    CHECK_SAVE(node_id, unbox(nid),
               // content index for hashed_node_id (1)
               1_b,
               // Process ID.
               0_b, 0_b, 0_b, 123_b,
               // Host ID.
               0_b, 1_b, 2_b, 3_b, 4_b, 5_b, 6_b, 7_b, 8_b, 9_b, //
               10_b, 11_b, 12_b, 13_b, 14_b, 15_b, 16_b, 17_b, 18_b, 19_b);
  }
  SUBTEST("custom struct") {
    caf::timestamp ts{caf::timestamp::duration{1478715821 * 1000000000ll}};
    test_data value{-345,
                    -1234567890123456789ll,
                    3.45,
                    54.3,
                    ts,
                    test_enum::b,
                    "Lorem ipsum dolor sit amet."};
    CHECK_SAVE(test_data, value,
               // 32-bit i32_ member: -345
               0xFF_b, 0xFF_b, 0xFE_b, 0xA7_b,
               // 64-bit i64_ member: -1234567890123456789ll
               0xEE_b, 0xDD_b, 0xEF_b, 0x0B_b, 0x82_b, 0x16_b, 0x7E_b, 0xEB_b,
               // 32-bit f32_ member: 3.45f
               0x40_b, 0x5C_b, 0xCC_b, 0xCD_b,
               // 64-bit f64_ member: 54.3
               0x40_b, 0x4B_b, 0x26_b, 0x66_b, 0x66_b, 0x66_b, 0x66_b, 0x66_b,
               // 64-bit ts_ member.
               0x14_b, 0x85_b, 0x74_b, 0x34_b, 0x62_b, 0x74_b, 0x82_b, 0x00_b,
               // 32-bit te_ member: test_enum::b
               0x00_b, 0x00_b, 0x00_b, 0x01_b,
               // str_ member:
               0x1B_b, //
               'L'_b, 'o'_b, 'r'_b, 'e'_b, 'm'_b, ' '_b, 'i'_b, 'p'_b, 's'_b,
               'u'_b, 'm'_b, ' '_b, 'd'_b, 'o'_b, 'l'_b, 'o'_b, 'r'_b, ' '_b,
               's'_b, 'i'_b, 't'_b, ' '_b, 'a'_b, 'm'_b, 'e'_b, 't'_b, '.'_b);
  }
  SUBTEST("enum class with non-default overload") {
    auto day = weekday::friday;
    CHECK_SAVE(weekday, day, 0x04_b);
  }
}

CAF_TEST_FIXTURE_SCOPE_END()
