// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2025 Google LLC
//
// Licensed under the Apache License v2.0 with LLVM Exceptions (the
// "License"); you may not use this file except in compliance with the
// License.  You may obtain a copy of the License at
//
//     https://llvm.org/LICENSE.txt
//
// 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.
//
// Author: Giuliano Procida

#include "number.h"

#include <array>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <sstream>
#include <vector>

#include <catch2/catch.hpp>

namespace {

using stg::Number;

template<typename T>
void CheckStrings(T n) {
  const Number number(n);
  std::ostringstream os1;
  os1 << (n + 0);  // promote char to int
  const auto n_as_string = os1.str();
  std::ostringstream os2;
  os2 << number;
  CHECK(!os2.fail());
  const auto number_as_string = os2.str();
  CHECK(n_as_string == number_as_string);
}

template<typename T>
void CheckAllStrings() {
  auto min = std::numeric_limits<T>::min();
  auto max = std::numeric_limits<T>::max();
  for (auto n = min; n < max; ++n) {
    CheckStrings(n);
  }
  CheckStrings(max);
}

template<typename T>
void CheckLimitStrings() {
  auto min = std::numeric_limits<T>::min();
  auto max = std::numeric_limits<T>::max();
  CheckStrings(min);
  CheckStrings(max);
}

TEST_CASE("all int8_t via Number to string") {
  CheckAllStrings<int8_t>();
}

TEST_CASE("all uint8_t via Number to string") {
  CheckAllStrings<uint8_t>();
}

TEST_CASE("all int16_t via Number to string") {
  CheckAllStrings<int16_t>();
}

TEST_CASE("all uint16_t via Number to string") {
  CheckAllStrings<uint16_t>();
}

TEST_CASE("min/max int32_t via Number to string") {
  CheckLimitStrings<int32_t>();
}

TEST_CASE("min/max uint32_t via Number to string") {
  CheckLimitStrings<uint32_t>();
}

TEST_CASE("min/max int64_t via Number to string") {
  CheckLimitStrings<int64_t>();
}

TEST_CASE("min/max uint64_t via Number to string") {
  CheckLimitStrings<uint64_t>();
}

template<typename T>
void CheckZeroChunks() {
  const Number zero;
  const Number four(4);
  for (size_t ix = 0; ix < 12; ++ix) {
    std::vector<T> chunks;
    chunks.resize(ix);
    const auto number = Number::FromChunks(chunks);
    CHECK(number == zero);
  }
  for (size_t ix = 1; ix < 12; ++ix) {
    std::vector<T> chunks;
    chunks.resize(ix);
    chunks[0] = 4;
    const auto number = Number::FromChunks(chunks);
    CHECK(number == four);
  }
}

TEST_CASE("zero chunks") {
  CheckZeroChunks<uint8_t>();
  CheckZeroChunks<uint16_t>();
  CheckZeroChunks<uint32_t>();
  CheckZeroChunks<uint64_t>();
}

TEST_CASE("Number to chunks and back") {
  for (int64_t a = 0; a < 2; ++a) {
    for (int64_t b = 0; b < 2; ++b) {
      for (int64_t c = 0; c < 2; ++c) {
        for (int64_t d = 0; d < 2; ++d) {
          for (int64_t e = 0; e < 2; ++e) {
            for (int64_t f = 0; f < 2; ++f) {
              for (int64_t g = 0; g < 2; ++g) {
                const int64_t n = (a << 56) + (b << 55) + (c << 32)
                                  + (d << 31) + (e << 8) + (f << 7) + g;
                GIVEN(n) {
                  const Number number(n);
                  const auto chunks8 = Number::ToChunks<uint8_t>(number);
                  const auto chunks16 = Number::ToChunks<uint16_t>(number);
                  const auto chunks32 = Number::ToChunks<uint32_t>(number);
                  const auto chunks64 = Number::ToChunks<uint64_t>(number);
                  const Number number8 = Number::FromChunks(chunks8);
                  const Number number16 = Number::FromChunks(chunks16);
                  const Number number32 = Number::FromChunks(chunks32);
                  const Number number64 = Number::FromChunks(chunks64);
                  CHECK(number8 == number);
                  CHECK(number16 == number);
                  CHECK(number32 == number);
                  CHECK(number64 == number);
                  CHECK(chunks64.size() == (n ? 1 : 0));
                  if (!chunks64.empty()) {
                    CHECK(static_cast<int64_t>(chunks64[0]) == n);
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

template<typename T>
void CheckChunks(const std::vector<T>& chunks) {
  const Number number = Number::FromChunks(chunks);
  std::ostringstream os;
  os << number;
  auto chunks1 = Number::ToChunks<T>(number);
  const T zeros = 0;
  const T ones = ~zeros;
  const T top = 1ULL << (sizeof(T) * 8 - 1);
  const T extension
      = chunks1.empty() || (chunks1.back() & top) == 0 ? zeros : ones;
  while (chunks1.size() < chunks.size()) {
    chunks1.push_back(extension);
  }
  CHECK(chunks1 == chunks);
}

TEST_CASE("1-byte chunks to Number and back") {
  for (size_t length = 0; length <= 10; ++length) {
    std::vector<uint8_t> chunks;
    chunks.resize(length);
    for (size_t bits = 0; bits < (1ULL << (2 * length)); ++bits) {
      for (size_t probe = 0; probe < length; ++probe) {
        uint8_t chunk = 0;
        if (bits & (1ULL << (2 * probe))) {
          chunk = chunk | 0x7f;
        }
        if (bits & (1ULL << (2 * probe + 1))) {
          chunk = chunk | 0x80;
        }
        chunks[probe] = chunk;
      }
      CheckChunks(chunks);
    }
  }
}

TEST_CASE("2-byte chunk to Number and back") {
  for (size_t length = 0; length <= 5; ++length) {
    std::vector<uint16_t> chunks;
    chunks.resize(length);
    for (size_t bits = 0; bits < (1ULL << (4 * length)); ++bits) {
      for (size_t probe = 0; probe < length; ++probe) {
        uint16_t chunk = 0;
        if (bits & (1ULL << (4 * probe))) {
          chunk = chunk | 0x7f;
        }
        if (bits & (1ULL << (4 * probe + 1))) {
          chunk = chunk | 0x80;
        }
        if (bits & (1ULL << (4 * probe + 2))) {
          chunk = chunk | 0x7f00;
        }
        if (bits & (1ULL << (4 * probe + 3))) {
          chunk = chunk | 0x8000;
        }
        chunks[probe] = chunk;
      }
      CheckChunks(chunks);
    }
  }
}

TEST_CASE("4-byte chunk to Number and back") {
  for (size_t length = 0; length <= 3; ++length) {
    std::vector<uint32_t> chunks;
    chunks.resize(length);
    for (size_t bits = 0; bits < (1ULL << (8 * length)); ++bits) {
      for (size_t probe = 0; probe < length; ++probe) {
        uint32_t chunk = 0;
        if (bits & (1ULL << (8 * probe))) {
          chunk = chunk | 0x7f;
        }
        if (bits & (1ULL << (8 * probe + 1))) {
          chunk = chunk | 0x80;
        }
        if (bits & (1ULL << (8 * probe + 2))) {
          chunk = chunk | 0x7f00;
        }
        if (bits & (1ULL << (8 * probe + 3))) {
          chunk = chunk | 0x8000;
        }
        if (bits & (1ULL << (8 * probe + 4))) {
          chunk = chunk | 0x7f0000;
        }
        if (bits & (1ULL << (8 * probe + 5))) {
          chunk = chunk | 0x800000;
        }
        if (bits & (1ULL << (8 * probe + 6))) {
          chunk = chunk | 0x7f000000;
        }
        if (bits & (1ULL << (8 * probe + 7))) {
          chunk = chunk | 0x80000000;
        }
        chunks[probe] = chunk;
      }
      CheckChunks(chunks);
    }
  }
}

TEST_CASE("8-byte chunk to Number and back") {
  for (size_t length = 0; length <= 2; ++length) {
    std::vector<uint64_t> chunks;
    chunks.resize(length);
    for (size_t bits = 0; bits < (1ULL << (8 * length)); ++bits) {
      for (size_t probe = 0; probe < length; ++probe) {
        uint64_t chunk = 0;
        if (bits & (1ULL << (8 * probe))) {
          chunk = chunk | 0x7f;
        }
        if (bits & (1ULL << (8 * probe + 1))) {
          chunk = chunk | 0x80;
        }
        if (bits & (1ULL << (8 * probe + 2))) {
          chunk = chunk | 0x7f00;
        }
        if (bits & (1ULL << (8 * probe + 3))) {
          chunk = chunk | 0x8000;
        }
        if (bits & (1ULL << (8 * probe + 4))) {
          chunk = chunk | 0x7fffffffff0000;
        }
        if (bits & (1ULL << (8 * probe + 5))) {
          chunk = chunk | 0x80000000000000;
        }
        if (bits & (1ULL << (8 * probe + 6))) {
          chunk = chunk | 0x7f00000000000000;
        }
        if (bits & (1ULL << (8 * probe + 7))) {
          chunk = chunk | 0x8000000000000000;
        }
        chunks[probe] = chunk;
      }
      CheckChunks(chunks);
    }
  }
}

constexpr std::array kPowersOf2 = {
  "1",
  "2",
  "4",
  "8",
  "16",
  "32",
  "64",
  "128",
  "256",
  "512",
  "1024",
  "2048",
  "4096",
  "8192",
  "16384",
  "32768",
  "65536",
  "131072",
  "262144",
  "524288",
  "1048576",
  "2097152",
  "4194304",
  "8388608",
  "16777216",
  "33554432",
  "67108864",
  "134217728",
  "268435456",
  "536870912",
  "1073741824",
  "2147483648",
  "4294967296",
  "8589934592",
  "17179869184",
  "34359738368",
  "68719476736",
  "137438953472",
  "274877906944",
  "549755813888",
  "1099511627776",
  "2199023255552",
  "4398046511104",
  "8796093022208",
  "17592186044416",
  "35184372088832",
  "70368744177664",
  "140737488355328",
  "281474976710656",
  "562949953421312",
  "1125899906842624",
  "2251799813685248",
  "4503599627370496",
  "9007199254740992",
  "18014398509481984",
  "36028797018963968",
  "72057594037927936",
  "144115188075855872",
  "288230376151711744",
  "576460752303423488",
  "1152921504606846976",
  "2305843009213693952",
  "4611686018427387904",
  "9223372036854775808",
  "18446744073709551616",
  "36893488147419103232",
  "73786976294838206464",
  "147573952589676412928",
  "295147905179352825856",
  "590295810358705651712",
  "1180591620717411303424",
  "2361183241434822606848",
  "4722366482869645213696",
  "9444732965739290427392",
  "18889465931478580854784",
  "37778931862957161709568",
  "75557863725914323419136",
  "151115727451828646838272",
  "302231454903657293676544",
  "604462909807314587353088",
  "1208925819614629174706176",
  "2417851639229258349412352",
  "4835703278458516698824704",
  "9671406556917033397649408",
  "19342813113834066795298816",
  "38685626227668133590597632",
  "77371252455336267181195264",
  "154742504910672534362390528",
  "309485009821345068724781056",
  "618970019642690137449562112",
  "1237940039285380274899124224",
  "2475880078570760549798248448",
  "4951760157141521099596496896",
  "9903520314283042199192993792",
  "19807040628566084398385987584",
  "39614081257132168796771975168",
  "79228162514264337593543950336",
  "158456325028528675187087900672",
  "316912650057057350374175801344",
  "633825300114114700748351602688",
  "1267650600228229401496703205376",
  "2535301200456458802993406410752",
  "5070602400912917605986812821504",
  "10141204801825835211973625643008",
  "20282409603651670423947251286016",
  "40564819207303340847894502572032",
  "81129638414606681695789005144064",
  "162259276829213363391578010288128",
  "324518553658426726783156020576256",
  "649037107316853453566312041152512",
  "1298074214633706907132624082305024",
  "2596148429267413814265248164610048",
  "5192296858534827628530496329220096",
  "10384593717069655257060992658440192",
  "20769187434139310514121985316880384",
  "41538374868278621028243970633760768",
  "83076749736557242056487941267521536",
  "166153499473114484112975882535043072",
  "332306998946228968225951765070086144",
  "664613997892457936451903530140172288",
  "1329227995784915872903807060280344576",
  "2658455991569831745807614120560689152",
  "5316911983139663491615228241121378304",
  "10633823966279326983230456482242756608",
  "21267647932558653966460912964485513216",
  "42535295865117307932921825928971026432",
  "85070591730234615865843651857942052864",
  "170141183460469231731687303715884105728",
  "340282366920938463463374607431768211456",
  "680564733841876926926749214863536422912",
  "1361129467683753853853498429727072845824",
  "2722258935367507707706996859454145691648",
  "5444517870735015415413993718908291383296",
  "10889035741470030830827987437816582766592",
  "21778071482940061661655974875633165533184",
  "43556142965880123323311949751266331066368",
  "87112285931760246646623899502532662132736",
  "174224571863520493293247799005065324265472",
  "348449143727040986586495598010130648530944",
  "696898287454081973172991196020261297061888",
  "1393796574908163946345982392040522594123776",
  "2787593149816327892691964784081045188247552",
  "5575186299632655785383929568162090376495104",
  "11150372599265311570767859136324180752990208",
  "22300745198530623141535718272648361505980416",
  "44601490397061246283071436545296723011960832",
  "89202980794122492566142873090593446023921664",
  "178405961588244985132285746181186892047843328",
  "356811923176489970264571492362373784095686656",
  "713623846352979940529142984724747568191373312",
  "1427247692705959881058285969449495136382746624",
  "2854495385411919762116571938898990272765493248",
  "5708990770823839524233143877797980545530986496",
  "11417981541647679048466287755595961091061972992",
  "22835963083295358096932575511191922182123945984",
  "45671926166590716193865151022383844364247891968",
  "91343852333181432387730302044767688728495783936",
  "182687704666362864775460604089535377456991567872",
  "365375409332725729550921208179070754913983135744",
  "730750818665451459101842416358141509827966271488",
  "1461501637330902918203684832716283019655932542976",
};

TEST_CASE("unsigned bytes powers") {
  for (size_t ix = 0; ix < kPowersOf2.size(); ++ix) {
    const auto& decimal = kPowersOf2[ix];
    const auto byte = ix / 8;
    const auto bit = ix % 8;
    std::vector<char> bytes(byte + 1, 0);
    bytes[byte] = 1 << bit;
    const auto number = Number::FromUnsignedBytes({bytes.data(), bytes.size()});
    std::ostringstream os;
    os << number;
    CHECK(os.str() == decimal);
    bytes.push_back(0);
    const auto padded = Number::FromUnsignedBytes({bytes.data(), bytes.size()});
    CHECK(padded == number);
  }
}

TEST_CASE("signed bytes powers") {
  for (size_t ix = 0; ix < kPowersOf2.size(); ++ix) {
    const auto& decimal = kPowersOf2[ix];
    const auto byte = ix / 8;
    const auto bit = ix % 8;
    std::vector<char> bytes(byte + (bit == 7 ? 2 : 1), 0);
    bytes[byte] = 1 << bit;
    const auto number = Number::FromSignedBytes({bytes.data(), bytes.size()});
    std::ostringstream os;
    os << number;
    CHECK(os.str() == decimal);
    bytes.push_back(0);
    const auto padded = Number::FromUnsignedBytes({bytes.data(), bytes.size()});
    CHECK(padded == number);
  }
}

TEST_CASE("negative bytes powers") {
  for (size_t ix = 0; ix < kPowersOf2.size(); ++ix) {
    const auto& decimal = kPowersOf2[ix];
    const auto byte = ix / 8;
    const auto bit = ix % 8;
    std::vector<char> bytes(byte + 1, 0);
    for (auto extension_bit = bit; extension_bit < 8; ++extension_bit) {
      bytes[byte] |= 1 << extension_bit;
    }
    const auto number = Number::FromSignedBytes({bytes.data(), bytes.size()});
    std::ostringstream os;
    os << number;
    std::ostringstream decimal_os;
    decimal_os << '-' << decimal;
    CHECK(os.str() == decimal_os.str());
    bytes.push_back(-1);
    const auto padded = Number::FromSignedBytes({bytes.data(), bytes.size()});
    CHECK(padded == number);
  }
}

}  // namespace
