// Copyright 2020 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_bytes/endian.h"

#include <array>
#include <cstddef>

#include "pw_bytes/array.h"
#include "pw_unit_test/constexpr.h"
#include "pw_unit_test/framework.h"

namespace pw::bytes {
namespace {

constexpr endian kNonNative =
    (endian::native == endian::little) ? endian::big : endian::little;

PW_CONSTEXPR_TEST(ConvertOrder, ToNativeUnsigned, {
  PW_TEST_EXPECT_EQ(ConvertOrderTo(endian::native, uint8_t{0x12}),
                    uint8_t{0x12});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(endian::native, uint16_t{0x0011}),
                    uint16_t{0x0011});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(endian::native, uint32_t{0x33221100}),
                    uint32_t{0x33221100});
  PW_TEST_EXPECT_EQ(
      ConvertOrderTo(endian::native, uint64_t{0x0011223344556677}),
      uint64_t{0x0011223344556677});
});

PW_CONSTEXPR_TEST(ConvertOrder, ToNativeSigned, {
  PW_TEST_EXPECT_EQ(ConvertOrderTo(endian::native, int8_t{0x12}), int8_t{0x12});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(endian::native, int16_t{0x0011}),
                    int16_t{0x0011});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(endian::native, int32_t{0x33221100}),
                    int32_t{0x33221100});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(endian::native, int64_t{0x0011223344556677}),
                    int64_t{0x0011223344556677});
});

PW_CONSTEXPR_TEST(ConvertOrder, FromNativeUnsigned, {
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(endian::native, uint8_t{0x12}),
                    uint8_t{0x12});
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(endian::native, uint16_t{0x0011}),
                    uint16_t{0x0011});
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(endian::native, uint32_t{0x33221100}),
                    uint32_t{0x33221100});
  PW_TEST_EXPECT_EQ(
      ConvertOrderFrom(endian::native, uint64_t{0x0011223344556677}),
      uint64_t{0x0011223344556677});
});

PW_CONSTEXPR_TEST(ConvertOrder, FromNativeSigned, {
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(endian::native, int8_t{0x12}),
                    int8_t{0x12});
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(endian::native, int16_t{0x0011}),
                    int16_t{0x0011});
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(endian::native, int32_t{0x33221100}),
                    int32_t{0x33221100});
  PW_TEST_EXPECT_EQ(
      ConvertOrderFrom(endian::native, int64_t{0x0011223344556677}),
      int64_t{0x0011223344556677});
});

PW_CONSTEXPR_TEST(ConvertOrder, ToNonNativeUnsigned, {
  PW_TEST_EXPECT_EQ(ConvertOrderTo(kNonNative, uint8_t{0x12}), uint8_t{0x12});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(kNonNative, uint16_t{0x0011}),
                    uint16_t{0x1100});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(kNonNative, uint32_t{0x33221100}),
                    uint32_t{0x00112233});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(kNonNative, uint64_t{0x0011223344556677}),
                    uint64_t{0x7766554433221100});
});

PW_CONSTEXPR_TEST(ConvertOrder, ToNonNativeSigned, {
  PW_TEST_EXPECT_EQ(ConvertOrderTo(kNonNative, int8_t{0x12}), int8_t{0x12});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(kNonNative, int16_t{0x0011}),
                    int16_t{0x1100});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(kNonNative, int32_t{0x33221100}),
                    int32_t{0x00112233});
  PW_TEST_EXPECT_EQ(ConvertOrderTo(kNonNative, int64_t{0x0011223344556677}),
                    int64_t{0x7766554433221100});
});

PW_CONSTEXPR_TEST(ConvertOrder, FromNonNativeUnsigned, {
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(kNonNative, uint8_t{0x12}), uint8_t{0x12});
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(kNonNative, uint16_t{0x0011}),
                    uint16_t{0x1100});
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(kNonNative, uint32_t{0x33221100}),
                    uint32_t{0x00112233});
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(kNonNative, uint64_t{0x0011223344556677}),
                    uint64_t{0x7766554433221100});
});

PW_CONSTEXPR_TEST(ConvertOrder, FromNonNativeSigned, {
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(kNonNative, int8_t{0x12}), int8_t{0x12});
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(kNonNative, int16_t{0x0011}),
                    int16_t{0x1100});
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(kNonNative, int32_t{0x33221100}),
                    int32_t{0x00112233});
  PW_TEST_EXPECT_EQ(ConvertOrderFrom(kNonNative, int64_t{0x0011223344556677}),
                    int64_t{0x7766554433221100});
});

template <typename T, typename U>
constexpr bool Equal(const T& lhs, const U& rhs) {
  if (sizeof(lhs) != sizeof(rhs) || std::size(lhs) != std::size(rhs)) {
    return false;
  }

  for (size_t i = 0; i < std::size(lhs); ++i) {
    if (lhs[i] != rhs[i]) {
      return false;
    }
  }

  return true;
}

PW_CONSTEXPR_TEST(CopyInOrder, 8bitLittle, {
  PW_TEST_EXPECT_TRUE(Equal(CopyInOrder(endian::little, '?'), Array<'?'>()));
  PW_TEST_EXPECT_TRUE(
      Equal(CopyInOrder(endian::little, uint8_t{0x10}), Array<0x10>()));
  PW_TEST_EXPECT_TRUE(Equal(
      CopyInOrder(endian::little, static_cast<int8_t>(0x10)), Array<0x10>()));
});

PW_CONSTEXPR_TEST(CopyInOrder, 8bitBig, {
  PW_TEST_EXPECT_TRUE(Equal(CopyInOrder(endian::big, '?'), Array<'?'>()));
  PW_TEST_EXPECT_TRUE(Equal(
      CopyInOrder(endian::big, static_cast<uint8_t>(0x10)), Array<0x10>()));
  PW_TEST_EXPECT_TRUE(Equal(CopyInOrder(endian::big, static_cast<int8_t>(0x10)),
                            Array<0x10>()));
});

PW_CONSTEXPR_TEST(CopyInOrder, 16bitLittle, {
  PW_TEST_EXPECT_TRUE(Equal(CopyInOrder(endian::little, uint16_t{0xAB12}),
                            Array<0x12, 0xAB>()));
  PW_TEST_EXPECT_TRUE(
      Equal(CopyInOrder(endian::little, static_cast<int16_t>(0xAB12)),
            Array<0x12, 0xAB>()));
});

PW_CONSTEXPR_TEST(CopyInOrder, 16bitBig, {
  PW_TEST_EXPECT_TRUE(
      Equal(CopyInOrder(endian::big, uint16_t{0xAB12}), Array<0xAB, 0x12>()));
  PW_TEST_EXPECT_TRUE(
      Equal(CopyInOrder(endian::big, static_cast<int16_t>(0xAB12)),
            Array<0xAB, 0x12>()));
});

PW_CONSTEXPR_TEST(CopyInOrder, 32bitLittle, {
  PW_TEST_EXPECT_TRUE(Equal(CopyInOrder(endian::little, uint32_t{0xAABBCCDD}),
                            Array<0xDD, 0xCC, 0xBB, 0xAA>()));
  PW_TEST_EXPECT_TRUE(
      Equal(CopyInOrder(endian::little, static_cast<int32_t>(0xAABBCCDD)),
            Array<0xDD, 0xCC, 0xBB, 0xAA>()));
});

PW_CONSTEXPR_TEST(CopyInOrder, 32bitBig, {
  PW_TEST_EXPECT_TRUE(Equal(CopyInOrder(endian::big, uint32_t{0xAABBCCDD}),
                            Array<0xAA, 0xBB, 0xCC, 0xDD>()));
  PW_TEST_EXPECT_TRUE(
      Equal(CopyInOrder(endian::big, static_cast<int32_t>(0xAABBCCDD)),
            Array<0xAA, 0xBB, 0xCC, 0xDD>()));
});

PW_CONSTEXPR_TEST(CopyInOrder, 64bitLittle, {
  PW_TEST_EXPECT_TRUE(
      Equal(CopyInOrder(endian::little, uint64_t{0xAABBCCDD11223344}),
            Array<0x44, 0x33, 0x22, 0x11, 0xDD, 0xCC, 0xBB, 0xAA>()));
  PW_TEST_EXPECT_TRUE(Equal(
      CopyInOrder(endian::little, static_cast<int64_t>(0xAABBCCDD11223344ull)),
      Array<0x44, 0x33, 0x22, 0x11, 0xDD, 0xCC, 0xBB, 0xAA>()));
});

PW_CONSTEXPR_TEST(CopyInOrder, 64bitBig, {
  PW_TEST_EXPECT_TRUE(
      Equal(CopyInOrder(endian::big, uint64_t{0xAABBCCDD11223344}),
            Array<0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44>()));
  PW_TEST_EXPECT_TRUE(Equal(
      CopyInOrder(endian::big, static_cast<int64_t>(0xAABBCCDD11223344ull)),
      Array<0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44>()));
});

PW_CONSTEXPR_TEST(CopyInOrderPointer, 8bit_StdByte, {
  std::byte buffer[1] = {};
  CopyInOrder(endian::little, uint8_t{0xAB}, buffer);
  PW_TEST_EXPECT_TRUE(Equal(buffer, Array<0xAB>()));
});

PW_CONSTEXPR_TEST(CopyInOrderPointer, 16bitLittle_StdByte, {
  std::byte buffer[2] = {};
  CopyInOrder(endian::little, uint16_t{0xABCD}, buffer);
  PW_TEST_EXPECT_TRUE(Equal(buffer, Array<0xCD, 0xAB>()));
});

PW_CONSTEXPR_TEST(CopyInOrderPointer, 32bitLittle_StdByte, {
  std::byte buffer[4] = {};
  CopyInOrder(endian::little, uint32_t{0xAABBCCDD}, buffer);
  PW_TEST_EXPECT_TRUE(Equal(buffer, Array<0xDD, 0xCC, 0xBB, 0xAA>()));
});

PW_CONSTEXPR_TEST(CopyInOrderPointer, 32bitBig_UnsignedChar, {
  unsigned char buffer[4] = {};
  CopyInOrder(endian::big, uint32_t{0xAABBCCDD}, buffer);
  PW_TEST_EXPECT_TRUE(
      Equal(buffer, Array<unsigned char, 0xAA, 0xBB, 0xCC, 0xDD>()));
});

PW_CONSTEXPR_TEST(CopyInOrderPointer, 64bitLittle_Char, {
  char buffer[8] = {};
  CopyInOrder(endian::little, uint64_t{0x0A0B0C0D11223344}, buffer);
  PW_TEST_EXPECT_TRUE(Equal(
      buffer, Array<char, 0x44, 0x33, 0x22, 0x11, 0x0D, 0x0C, 0x0B, 0x0A>()));
});

PW_CONSTEXPR_TEST(CopyInOrderPointer, 64bitBig_SignedChar, {
  signed char buffer[8] = {};
  CopyInOrder(endian::big, uint64_t{0x0A0B0C0D11223344}, buffer);
  PW_TEST_EXPECT_TRUE(Equal(
      buffer,
      Array<signed char, 0x0A, 0x0B, 0x0C, 0x0D, 0x11, 0x22, 0x33, 0x44>()));
});

constexpr const char* kNumber = "\x11\x22\x33\x44\xaa\xbb\xcc\xdd";

TEST(ReadInOrder, 8Bit_Big) {
  EXPECT_EQ(ReadInOrder<uint8_t>(endian::big, "\0"), 0u);
  EXPECT_EQ(ReadInOrder<uint8_t>(endian::big, "\x80"), 0x80u);
  EXPECT_EQ(ReadInOrder<uint8_t>(endian::big, kNumber), 0x11u);

  EXPECT_EQ(ReadInOrder<int8_t>(endian::big, "\0"), 0);
  EXPECT_EQ(ReadInOrder<int8_t>(endian::big, "\x80"), -128);
  EXPECT_EQ(ReadInOrder<int8_t>(endian::big, kNumber), 0x11);
}

TEST(ReadInOrder, 8Bit_Little) {
  EXPECT_EQ(ReadInOrder<uint8_t>(endian::little, "\0"), 0u);
  EXPECT_EQ(ReadInOrder<uint8_t>(endian::little, "\x80"), 0x80u);
  EXPECT_EQ(ReadInOrder<uint8_t>(endian::little, kNumber), 0x11u);

  EXPECT_EQ(ReadInOrder<int8_t>(endian::little, "\0"), 0);
  EXPECT_EQ(ReadInOrder<int8_t>(endian::little, "\x80"), -128);
  EXPECT_EQ(ReadInOrder<int8_t>(endian::little, kNumber), 0x11);
}

TEST(ReadInOrder, 16Bit_Big) {
  EXPECT_EQ(ReadInOrder<uint16_t>(endian::big, "\0\0"), 0u);
  EXPECT_EQ(ReadInOrder<uint16_t>(endian::big, "\x80\0"), 0x8000u);
  EXPECT_EQ(ReadInOrder<uint16_t>(endian::big, kNumber), 0x1122u);

  EXPECT_EQ(ReadInOrder<int16_t>(endian::big, "\0\0"), 0);
  EXPECT_EQ(ReadInOrder<int16_t>(endian::big, "\x80\0"), -32768);
  EXPECT_EQ(ReadInOrder<int16_t>(endian::big, kNumber), 0x1122);
}

TEST(ReadInOrder, 16Bit_Little) {
  EXPECT_EQ(ReadInOrder<uint16_t>(endian::little, "\0\0"), 0u);
  EXPECT_EQ(ReadInOrder<uint16_t>(endian::little, "\x80\0"), 0x80u);
  EXPECT_EQ(ReadInOrder<uint16_t>(endian::little, kNumber), 0x2211u);

  EXPECT_EQ(ReadInOrder<int16_t>(endian::little, "\0\0"), 0);
  EXPECT_EQ(ReadInOrder<int16_t>(endian::little, "\x80\0"), 0x80);
  EXPECT_EQ(ReadInOrder<int16_t>(endian::little, kNumber), 0x2211);
}

TEST(ReadInOrder, 32Bit_Big) {
  EXPECT_EQ(ReadInOrder<uint32_t>(endian::big, "\0\0\0\0"), 0u);
  EXPECT_EQ(ReadInOrder<uint32_t>(endian::big, "\x80\0\0\0"), 0x80000000u);
  EXPECT_EQ(ReadInOrder<uint32_t>(endian::big, kNumber), 0x11223344u);

  EXPECT_EQ(ReadInOrder<int32_t>(endian::big, "\0\0\0\0"), 0);
  EXPECT_EQ(ReadInOrder<int32_t>(endian::big, "\x80\0\0\0"), -2147483648);
  EXPECT_EQ(ReadInOrder<int32_t>(endian::big, kNumber), 0x11223344);
}

TEST(ReadInOrder, 32Bit_Little) {
  EXPECT_EQ(ReadInOrder<uint32_t>(endian::little, "\0\0\0\0"), 0u);
  EXPECT_EQ(ReadInOrder<uint32_t>(endian::little, "\x80\0\0\0"), 0x80u);
  EXPECT_EQ(ReadInOrder<uint32_t>(endian::little, kNumber), 0x44332211u);

  EXPECT_EQ(ReadInOrder<int32_t>(endian::little, "\0\0\0\0"), 0);
  EXPECT_EQ(ReadInOrder<int32_t>(endian::little, "\x80\0\0\0"), 0x80);
  EXPECT_EQ(ReadInOrder<int32_t>(endian::little, kNumber), 0x44332211);
}

TEST(ReadInOrder, 64Bit_Big) {
  EXPECT_EQ(ReadInOrder<uint64_t>(endian::big, "\0\0\0\0\0\0\0\0"), 0u);
  EXPECT_EQ(ReadInOrder<uint64_t>(endian::big, "\x80\0\0\0\0\0\0\0"),
            0x80000000'00000000llu);
  EXPECT_EQ(ReadInOrder<uint64_t>(endian::big, kNumber), 0x11223344AABBCCDDu);

  EXPECT_EQ(ReadInOrder<int64_t>(endian::big, "\0\0\0\0\0\0\0\0"), 0);
  EXPECT_EQ(ReadInOrder<int64_t>(endian::big, "\x80\0\0\0\0\0\0\0"),
            static_cast<int64_t>(1llu << 63));
  EXPECT_EQ(ReadInOrder<int64_t>(endian::big, kNumber), 0x11223344AABBCCDD);
}

TEST(ReadInOrder, 64Bit_Little) {
  EXPECT_EQ(ReadInOrder<uint64_t>(endian::little, "\0\0\0\0\0\0\0\0"), 0u);
  EXPECT_EQ(ReadInOrder<uint64_t>(endian::little, "\x80\0\0\0\0\0\0\0"), 0x80u);
  EXPECT_EQ(ReadInOrder<uint64_t>(endian::little, kNumber),
            0xDDCCBBAA44332211u);

  EXPECT_EQ(ReadInOrder<int64_t>(endian::little, "\0\0\0\0\0\0\0\0"), 0);
  EXPECT_EQ(ReadInOrder<int64_t>(endian::little, "\x80\0\0\0\0\0\0\0"), 0x80);
  EXPECT_EQ(ReadInOrder<int64_t>(endian::little, kNumber),
            static_cast<int64_t>(0xDDCCBBAA44332211));
}

TEST(ReadInOrder, StdArray) {
  std::array<std::byte, 4> buffer = Array<1, 2, 3, 4>();
  EXPECT_EQ(0x04030201, ReadInOrder<int32_t>(endian::little, buffer));
  EXPECT_EQ(0x01020304, ReadInOrder<int32_t>(endian::big, buffer));
}

TEST(ReadInOrder, CArray) {
  char buffer[5] = {1, 2, 3, 4, 99};
  EXPECT_EQ(0x04030201, ReadInOrder<int32_t>(endian::little, buffer));
  EXPECT_EQ(0x01020304, ReadInOrder<int32_t>(endian::big, buffer));
}

TEST(ReadInOrder, BoundsChecking_Ok) {
  constexpr auto buffer = Array<1, 2, 3, 4>();
  uint16_t value = 0;
  EXPECT_TRUE(ReadInOrder(endian::little, buffer, value));
  EXPECT_EQ(0x0201, value);
}

TEST(ReadInOrder, BoundsChecking_TooSmall) {
  constexpr auto buffer = Array<1, 2, 3>();
  int32_t value = 0;
  EXPECT_FALSE(ReadInOrder(endian::little, buffer, value));
  EXPECT_EQ(0, value);
}

TEST(ReadInOrder, PartialLittleEndian) {
  constexpr auto buffer = Array<1, 2, 3, 4>();

  EXPECT_EQ(0x00000000, ReadInOrder<int32_t>(endian::little, buffer.data(), 0));
  EXPECT_EQ(0x00000001, ReadInOrder<int32_t>(endian::little, buffer.data(), 1));
  EXPECT_EQ(0x00000201, ReadInOrder<int32_t>(endian::little, buffer.data(), 2));
  EXPECT_EQ(0x00030201, ReadInOrder<int32_t>(endian::little, buffer.data(), 3));
  EXPECT_EQ(0x04030201, ReadInOrder<int32_t>(endian::little, buffer.data(), 4));
  EXPECT_EQ(0x04030201, ReadInOrder<int32_t>(endian::little, buffer.data(), 5));
  EXPECT_EQ(0x04030201,
            ReadInOrder<int32_t>(endian::little, buffer.data(), 100));
}

TEST(ReadInOrder, PartialBigEndian) {
  constexpr auto buffer = Array<1, 2, 3, 4>();

  EXPECT_EQ(0x00000000, ReadInOrder<int32_t>(endian::big, buffer.data(), 0));
  EXPECT_EQ(0x01000000, ReadInOrder<int32_t>(endian::big, buffer.data(), 1));
  EXPECT_EQ(0x01020000, ReadInOrder<int32_t>(endian::big, buffer.data(), 2));
  EXPECT_EQ(0x01020300, ReadInOrder<int32_t>(endian::big, buffer.data(), 3));
  EXPECT_EQ(0x01020304, ReadInOrder<int32_t>(endian::big, buffer.data(), 4));
  EXPECT_EQ(0x01020304, ReadInOrder<int32_t>(endian::big, buffer.data(), 5));
  EXPECT_EQ(0x01020304, ReadInOrder<int32_t>(endian::big, buffer.data(), 100));
}

}  // namespace
}  // namespace pw::bytes
