//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++ (supporting code)
// |  |  |__   |  |  | | | |  version 3.11.3
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

#include "doctest_compatibility.h"

#include <azure/core/internal/json/json.hpp>

#include <algorithm>
using Azure::Core::Json::_internal::json;

TEST_CASE("algorithms")
{
  json j_array = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz"};
  json j_object = {{"one", 1}, {"two", 2}};

  SECTION("non-modifying sequence operations")
  {
    SECTION("std::all_of")
    {
      CHECK(std::all_of(
          j_array.begin(), j_array.end(), [](const json& value) { return !value.empty(); }));
      CHECK(std::all_of(j_object.begin(), j_object.end(), [](const json& value) {
        return value.type() == json::value_t::number_integer;
      }));
    }

    SECTION("std::any_of")
    {
      CHECK(std::any_of(j_array.begin(), j_array.end(), [](const json& value) {
        return value.is_string() && value.get<std::string>() == "foo";
      }));
      CHECK(std::any_of(j_object.begin(), j_object.end(), [](const json& value) {
        return value.get<int>() > 1;
      }));
    }

    SECTION("std::none_of")
    {
      CHECK(std::none_of(
          j_array.begin(), j_array.end(), [](const json& value) { return value.empty(); }));
      CHECK(std::none_of(j_object.begin(), j_object.end(), [](const json& value) {
        return value.get<int>() <= 0;
      }));
    }

    SECTION("std::for_each")
    {
      SECTION("reading")
      {
        int sum = 0;

        std::for_each(j_array.cbegin(), j_array.cend(), [&sum](const json& value) {
          if (value.is_number())
          {
            sum += static_cast<int>(value);
          }
        });

        CHECK(sum == 45);
      }

      SECTION("writing")
      {
        auto add17 = [](json& value) {
          if (value.is_array())
          {
            value.push_back(17);
          }
        };

        std::for_each(j_array.begin(), j_array.end(), add17);

        CHECK(j_array[6] == json({1, 2, 3, 17}));
      }
    }

    SECTION("std::count") { CHECK(std::count(j_array.begin(), j_array.end(), json(true)) == 1); }

    SECTION("std::count_if")
    {
      CHECK(
          std::count_if(
              j_array.begin(), j_array.end(), [](const json& value) { return (value.is_number()); })
          == 3);
      CHECK(std::count_if(j_array.begin(), j_array.end(), [](const json&) { return true; }) == 9);
    }

    SECTION("std::mismatch")
    {
      json j_array2 = {
          13, 29, 3, {{"one", 1}, {"two", 2}, {"three", 3}}, true, false, {1, 2, 3}, "foo", "baz"};
      auto res = std::mismatch(j_array.begin(), j_array.end(), j_array2.begin());
      CHECK(*res.first == json({{"one", 1}, {"two", 2}}));
      CHECK(*res.second == json({{"one", 1}, {"two", 2}, {"three", 3}}));
    }

    SECTION("std::equal")
    {
      SECTION("using operator==")
      {
        CHECK(std::equal(j_array.begin(), j_array.end(), j_array.begin()));
        CHECK(std::equal(j_object.begin(), j_object.end(), j_object.begin()));
        CHECK(!std::equal(j_array.begin(), j_array.end(), j_object.begin()));
      }

      SECTION("using user-defined comparison")
      {
        // compare objects only by size of its elements
        json j_array2
            = {13,
               29,
               3,
               {"Hello", "World"},
               true,
               false,
               {{"one", 1}, {"two", 2}, {"three", 3}},
               "foo",
               "baz"};
        CHECK(!std::equal(j_array.begin(), j_array.end(), j_array2.begin()));
        CHECK(std::equal(
            j_array.begin(), j_array.end(), j_array2.begin(), [](const json& a, const json& b) {
              return (a.size() == b.size());
            }));
      }
    }

    SECTION("std::find")
    {
      auto it = std::find(j_array.begin(), j_array.end(), json(false));
      CHECK(std::distance(j_array.begin(), it) == 5);
    }

    SECTION("std::find_if")
    {
      auto it = std::find_if(
          j_array.begin(), j_array.end(), [](const json& value) { return value.is_boolean(); });
      CHECK(std::distance(j_array.begin(), it) == 4);
    }

    SECTION("std::find_if_not")
    {
      auto it = std::find_if_not(
          j_array.begin(), j_array.end(), [](const json& value) { return value.is_number(); });
      CHECK(std::distance(j_array.begin(), it) == 3);
    }

    SECTION("std::adjacent_find")
    {
      CHECK(std::adjacent_find(j_array.begin(), j_array.end()) == j_array.end());
      CHECK(
          std::adjacent_find(
              j_array.begin(),
              j_array.end(),
              [](const json& v1, const json& v2) { return v1.type() == v2.type(); })
          == j_array.begin());
    }
  }

  SECTION("modifying sequence operations")
  {
    SECTION("std::reverse")
    {
      std::reverse(j_array.begin(), j_array.end());
      CHECK(
          j_array
          == json({"baz", "foo", {1, 2, 3}, false, true, {{"one", 1}, {"two", 2}}, 3, 29, 13}));
    }

    SECTION("std::rotate")
    {
      std::rotate(j_array.begin(), j_array.begin() + 1, j_array.end());
      CHECK(
          j_array
          == json({29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", 13}));
    }

    SECTION("std::partition")
    {
      auto it = std::partition(
          j_array.begin(), j_array.end(), [](const json& v) { return v.is_string(); });
      CHECK(std::distance(j_array.begin(), it) == 2);
      CHECK(!it[2].is_string());
    }
  }

  SECTION("sorting operations")
  {
    SECTION("std::sort")
    {
      SECTION("with standard comparison")
      {
        json j
            = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
        std::sort(j.begin(), j.end());
        CHECK(
            j
            == json(
                {nullptr,
                 false,
                 true,
                 3,
                 13,
                 29,
                 {{"one", 1}, {"two", 2}},
                 {1, 2, 3},
                 "baz",
                 "foo"}));
      }

      SECTION("with user-defined comparison")
      {
        json j = {3, {{"one", 1}, {"two", 2}}, {1, 2, 3}, nullptr};
        std::sort(
            j.begin(), j.end(), [](const json& a, const json& b) { return a.size() < b.size(); });
        CHECK(j == json({nullptr, 3, {{"one", 1}, {"two", 2}}, {1, 2, 3}}));
      }

      SECTION("sorting an object")
      {
        json j({{"one", 1}, {"two", 2}});
        CHECK_THROWS_WITH_AS(
            std::sort(j.begin(), j.end()),
            "[json.exception.invalid_iterator.209] cannot use offsets with object iterators",
            json::invalid_iterator&);
      }
    }

    SECTION("std::partial_sort")
    {
      json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
      std::partial_sort(j.begin(), j.begin() + 4, j.end());
      CHECK(
          j
          == json(
              {nullptr,
               false,
               true,
               3,
               {{"one", 1}, {"two", 2}},
               29,
               {1, 2, 3},
               "foo",
               "baz",
               13}));
    }
  }

  SECTION("set operations")
  {
    SECTION("std::merge")
    {
      {
        json j1 = {2, 4, 6, 8};
        json j2 = {1, 2, 3, 5, 7};
        json j3;

        std::merge(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
        CHECK(j3 == json({1, 2, 2, 3, 4, 5, 6, 7, 8}));
      }
    }

    SECTION("std::set_difference")
    {
      json j1 = {1, 2, 3, 4, 5, 6, 7, 8};
      json j2 = {1, 2, 3, 5, 7};
      json j3;

      std::set_difference(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
      CHECK(j3 == json({4, 6, 8}));
    }

    SECTION("std::set_intersection")
    {
      json j1 = {1, 2, 3, 4, 5, 6, 7, 8};
      json j2 = {1, 2, 3, 5, 7};
      json j3;

      std::set_intersection(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
      CHECK(j3 == json({1, 2, 3, 5, 7}));
    }

    SECTION("std::set_union")
    {
      json j1 = {2, 4, 6, 8};
      json j2 = {1, 2, 3, 5, 7};
      json j3;

      std::set_union(j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
      CHECK(j3 == json({1, 2, 3, 4, 5, 6, 7, 8}));
    }

    SECTION("std::set_symmetric_difference")
    {
      json j1 = {2, 4, 6, 8};
      json j2 = {1, 2, 3, 5, 7};
      json j3;

      std::set_symmetric_difference(
          j1.begin(), j1.end(), j2.begin(), j2.end(), std::back_inserter(j3));
      CHECK(j3 == json({1, 3, 4, 5, 6, 7, 8}));
    }
  }

  SECTION("heap operations")
  {
    std::make_heap(j_array.begin(), j_array.end());
    CHECK(std::is_heap(j_array.begin(), j_array.end()));
    std::sort_heap(j_array.begin(), j_array.end());
    CHECK(
        j_array
        == json({false, true, 3, 13, 29, {{"one", 1}, {"two", 2}}, {1, 2, 3}, "baz", "foo"}));
  }

  SECTION("iota")
  {
    SECTION("int")
    {
      json json_arr = {0, 5, 2, 4, 10, 20, 30, 40, 50, 1};
      std::iota(json_arr.begin(), json_arr.end(), 0);
      CHECK(json_arr == json({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}));
    }
    SECTION("double")
    {
      json json_arr = {0.5, 1.5, 1.3, 4.1, 10.2, 20.5, 30.6, 40.1, 50.22, 1.5};
      std::iota(json_arr.begin(), json_arr.end(), 0.5);
      CHECK(json_arr == json({0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}));
    }

    SECTION("char")
    {
      json json_arr = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '0', '1'};
      std::iota(json_arr.begin(), json_arr.end(), '0');
      CHECK(json_arr == json({'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}));
    }
  }

  SECTION("copy")
  {
    SECTION("copy without if")
    {
      json dest_arr;
      const json source_arr = {1, 2, 3, 4};

      std::copy(source_arr.begin(), source_arr.end(), std::back_inserter(dest_arr));

      CHECK(dest_arr == source_arr);
    }
    SECTION("copy if")
    {
      json dest_arr;
      const json source_arr = {0, 3, 6, 9, 12, 15, 20};

      std::copy_if(
          source_arr.begin(),
          source_arr.end(),
          std::back_inserter(dest_arr),
          [](const json& _value) { return _value.get<int>() % 3 == 0; });
      CHECK(dest_arr == json({0, 3, 6, 9, 12, 15}));
    }
    SECTION("copy n")
    {
      const json source_arr = {0, 1, 2, 3, 4, 5, 6, 7};
      json dest_arr;
      const unsigned char numToCopy = 2;

      std::copy_n(source_arr.begin(), numToCopy, std::back_inserter(dest_arr));
      CHECK(dest_arr == json{0, 1});
    }
    SECTION("copy n chars")
    {
      const json source_arr = {'1', '2', '3', '4', '5', '6', '7'};
      json dest_arr;
      const unsigned char numToCopy = 4;

      std::copy_n(source_arr.begin(), numToCopy, std::back_inserter(dest_arr));
      CHECK(dest_arr == json{'1', '2', '3', '4'});
    }
  }
}
