// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "lib/jxl/coeff_order.h"

#include <jxl/memory_manager.h>

#include <algorithm>
#include <cstddef>
#include <cstdio>
#include <numeric>  // iota
#include <utility>
#include <vector>

#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/base/random.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/coeff_order_fwd.h"
#include "lib/jxl/dec_bit_reader.h"
#include "lib/jxl/enc_aux_out.h"
#include "lib/jxl/enc_bit_writer.h"
#include "lib/jxl/enc_coeff_order.h"
#include "lib/jxl/test_memory_manager.h"
#include "lib/jxl/testing.h"

namespace jxl {
namespace {

void RoundtripPermutation(coeff_order_t* perm, coeff_order_t* out, size_t len,
                          size_t* size) {
  JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
  BitWriter writer{memory_manager};
  ASSERT_TRUE(
      EncodePermutation(perm, 0, len, &writer, LayerType::Header, nullptr));
  writer.ZeroPadToByte();
  Status status = true;
  Bytes bytes = writer.GetSpan();
  {
    BitReader reader(bytes);
    BitReaderScopedCloser closer(reader, status);
    ASSERT_TRUE(DecodePermutation(memory_manager, 0, len, out, &reader));
  }
  ASSERT_TRUE(status);
  *size = bytes.size();
}

enum Permutation { kIdentity, kFewSwaps, kFewSlides, kRandom };

constexpr size_t kSwaps = 32;

void TestPermutation(Permutation kind, size_t len) {
  std::vector<coeff_order_t> perm(len);
  std::iota(perm.begin(), perm.end(), 0);
  Rng rng(0);
  if (kind == kFewSwaps) {
    for (size_t i = 0; i < kSwaps; i++) {
      size_t a = rng.UniformU(0, len - 1);
      size_t b = rng.UniformU(0, len - 1);
      std::swap(perm[a], perm[b]);
    }
  }
  if (kind == kFewSlides) {
    for (size_t i = 0; i < kSwaps; i++) {
      size_t a = rng.UniformU(0, len - 1);
      size_t b = rng.UniformU(0, len - 1);
      size_t from = std::min(a, b);
      size_t to = std::max(a, b);
      size_t start = perm[from];
      for (size_t j = from; j < to; j++) {
        perm[j] = perm[j + 1];
      }
      perm[to] = start;
    }
  }
  if (kind == kRandom) {
    rng.Shuffle(perm.data(), perm.size());
  }
  std::vector<coeff_order_t> out(len);
  size_t size = 0;
  RoundtripPermutation(perm.data(), out.data(), len, &size);
  for (size_t idx = 0; idx < len; idx++) {
    EXPECT_EQ(perm[idx], out[idx]);
  }
  printf("Encoded size: %" PRIuS "\n", size);
}

TEST(CoeffOrderTest, IdentitySmall) { TestPermutation(kIdentity, 256); }
TEST(CoeffOrderTest, FewSlidesSmall) { TestPermutation(kFewSlides, 256); }
TEST(CoeffOrderTest, FewSwapsSmall) { TestPermutation(kFewSwaps, 256); }
TEST(CoeffOrderTest, RandomSmall) { TestPermutation(kRandom, 256); }

TEST(CoeffOrderTest, IdentityMedium) { TestPermutation(kIdentity, 1 << 12); }
TEST(CoeffOrderTest, FewSlidesMedium) { TestPermutation(kFewSlides, 1 << 12); }
TEST(CoeffOrderTest, FewSwapsMedium) { TestPermutation(kFewSwaps, 1 << 12); }
TEST(CoeffOrderTest, RandomMedium) { TestPermutation(kRandom, 1 << 12); }

TEST(CoeffOrderTest, IdentityBig) { TestPermutation(kIdentity, 1 << 16); }
TEST(CoeffOrderTest, FewSlidesBig) { TestPermutation(kFewSlides, 1 << 16); }
TEST(CoeffOrderTest, FewSwapsBig) { TestPermutation(kFewSwaps, 1 << 16); }
TEST(CoeffOrderTest, RandomBig) { TestPermutation(kRandom, 1 << 16); }

}  // namespace
}  // namespace jxl
