/** @file

    Scalar unit testing.

    @section license License

    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you 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

    http://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 <catch.hpp>
#include "tscore/Scalar.h"
//#include <string>
//#include <cstdarg>
//#include <iostream>

using Bytes      = ts::Scalar<1, off_t>;
using Paragraphs = ts::Scalar<16, off_t>;
using KB         = ts::Scalar<1024, off_t>;
using MB         = ts::Scalar<KB::SCALE * 1024, off_t>;

TEST_CASE("Scalar", "[libts][Scalar]")
{
  constexpr static int SCALE   = 4096;
  constexpr static int SCALE_1 = 8192;
  constexpr static int SCALE_2 = 512;

  using PageSize = ts::Scalar<SCALE>;

  PageSize pg1(1);
  REQUIRE(pg1.count() == 1);
  REQUIRE(pg1.value() == SCALE);

  using Size_1 = ts::Scalar<SCALE_1>;
  using Size_2 = ts::Scalar<SCALE_2>;

  Size_2 sz_a(2);
  Size_2 sz_b(57);
  Size_2 sz_c(SCALE_1 / SCALE_2);
  Size_2 sz_d(29 * SCALE_1 / SCALE_2);

  Size_1 sz = ts::round_up(sz_a);
  REQUIRE(sz.count() == 1);
  sz = ts::round_down(sz_a);
  REQUIRE(sz.count() == 0);

  sz = ts::round_up(sz_b);
  REQUIRE(sz.count() == 4);
  sz = ts::round_down(sz_b);
  REQUIRE(sz.count() == 3);

  sz = ts::round_up(sz_c);
  REQUIRE(sz.count() == 1);
  sz = ts::round_down(sz_c);
  REQUIRE(sz.count() == 1);

  sz = ts::round_up(sz_d);
  REQUIRE(sz.count() == 29);
  sz = ts::round_down(sz_d);
  REQUIRE(sz.count() == 29);

  sz.assign(119);
  sz_b = sz; // Should be OK because SCALE_1 is an integer multiple of SCALE_2
  //  sz = sz_b; // Should not compile.
  REQUIRE(sz_b.count() == 119 * (SCALE_1 / SCALE_2));
}

TEST_CASE("Scalar Factors", "[libts][Scalar][factors]")
{
  constexpr static int SCALE_1 = 30;
  constexpr static int SCALE_2 = 20;

  using Size_1 = ts::Scalar<SCALE_1>;
  using Size_2 = ts::Scalar<SCALE_2>;

  Size_2 sz_a(2);
  Size_2 sz_b(97);

  Size_1 sz = round_up(sz_a);
  REQUIRE(sz.count() == 2);
  sz = round_down(sz_a);
  REQUIRE(sz.count() == 1);

  sz = ts::round_up(sz_b);
  REQUIRE(sz.count() == 65);
  sz = ts::round_down(sz_b);
  REQUIRE(sz.count() == 64);

  ts::Scalar<9> m_9;
  ts::Scalar<4> m_4, m_test;

  m_9.assign(95);
  //  m_4 = m_9; // Should fail to compile with static assert.
  //  m_9 = m_4; // Should fail to compile with static assert.

  m_4 = ts::round_up(m_9);
  REQUIRE(m_4.count() == 214);
  m_4 = ts::round_down(m_9);
  REQUIRE(m_4.count() == 213);

  m_4.assign(213);
  m_9 = ts::round_up(m_4);
  REQUIRE(m_9.count() == 95);
  m_9 = ts::round_down(m_4);
  REQUIRE(m_9.count() == 94);

  m_test = m_4; // Verify assignment of identical scale values compiles.
  REQUIRE(m_test.count() == 213);
}

TEST_CASE("Scalar Arithmetic", "[libts][Scalar][arithmetic]")
{
  using KBytes  = ts::Scalar<1024>;
  using KiBytes = ts::Scalar<1024, long int>;
  using Bytes   = ts::Scalar<1, int64_t>;
  using MBytes  = ts::Scalar<1024 * KBytes::SCALE>;

  Bytes bytes(96);
  KBytes kbytes(2);
  MBytes mbytes(5);

  Bytes z1 = ts::round_up(bytes + 128);
  REQUIRE(z1.count() == 224);
  KBytes z2 = kbytes + kbytes(3);
  REQUIRE(z2.count() == 5);
  Bytes z3(bytes);
  z3 += kbytes;
  REQUIRE(z3.value() == 2048 + 96);
  MBytes z4 = mbytes;
  z4.inc(5);
  z2 += z4;
  REQUIRE(z2.value() == (10 << 20) + (5 << 10));

  z1.inc(128);
  REQUIRE(z1.count() == 352);

  z2.assign(2);
  z1 = 3 * z2;
  REQUIRE(z1.count() == 6144);
  z1 *= 5;
  REQUIRE(z1.count() == 30720);
  z1 /= 3;
  REQUIRE(z1.count() == 10240);

  z2.assign(3148);
  auto x = z2 + MBytes(1);
  REQUIRE(x.scale() == z2.scale());
  REQUIRE(x.count() == 4172);

  z2 = ts::round_down(262150);
  REQUIRE(z2.count() == 256);

  z2 = ts::round_up(262150);
  REQUIRE(z2.count() == 257);

  KBytes q(ts::round_down(262150));
  REQUIRE(q.count() == 256);

  z2 += ts::round_up(97384);
  REQUIRE(z2.count() == 353);

  decltype(z2) a = ts::round_down(z2 + 167229);
  REQUIRE(a.count() == 516);

  KiBytes k(3148);
  auto kx = k + MBytes(1);
  REQUIRE(kx.scale() == k.scale());
  REQUIRE(kx.count() == 4172);

  k = ts::round_down(262150);
  REQUIRE(k.count() == 256);

  k = ts::round_up(262150);
  REQUIRE(k.count() == 257);

  KBytes kq(ts::round_down(262150));
  REQUIRE(kq.count() == 256);

  k += ts::round_up(97384);
  REQUIRE(k.count() == 353);

  decltype(k) ka = ts::round_down(k + 167229);
  REQUIRE(ka.count() == 516);

  using StoreBlocks = ts::Scalar<8 * KB::SCALE, off_t>;
  using SpanBlocks  = ts::Scalar<127 * MB::SCALE, off_t>;

  StoreBlocks store_b(80759700);
  SpanBlocks span_b(4968);
  SpanBlocks delta(1);

  REQUIRE(store_b < span_b);
  REQUIRE(span_b < store_b + delta);
  store_b += delta;
  REQUIRE(span_b < store_b);

  static const off_t N = 7 * 1024;
  Bytes b(N + 384);
  KB kb(round_down(b));

  REQUIRE(kb == N);
  REQUIRE(kb < N + 1);
  REQUIRE(kb > N - 1);

  REQUIRE(kb < b);
  REQUIRE(kb <= b);
  REQUIRE(b > kb);
  REQUIRE(b >= kb);

  ++kb;

  REQUIRE(b < kb);
  REQUIRE(b <= kb);
  REQUIRE(kb > b);
  REQUIRE(kb >= b);
}

#if 0
struct KBytes_tag {
  static std::string const label;
};
std::string const KBytes_tag::label(" bytes");

void
Test_IO()
{
  typedef ts::Scalar<1024, long int, KBytes_tag> KBytes;
  typedef ts::Scalar<1024, int> KiBytes;

  KBytes x(12);
  KiBytes y(12);

  std::cout << "Testing" << std::endl;
  std::cout << "x is " << x << std::endl;
  std::cout << "y is " << y << std::endl;
}

void
test_Compile()
{
  // These tests aren't normally run, they exist to detect compiler issues.

  typedef ts::Scalar<1024, short> KBytes;
  typedef ts::Scalar<1024, int> KiBytes;
  int delta = 10;

  KBytes x(12);
  KiBytes y(12);

  if (x > 12) {
    std::cout << "Operator > works" << std::endl;
  }
  if (y > 12) {
    std::cout << "Operator > works" << std::endl;
  }

  (void)(x.inc(10));
  (void)(x.inc(static_cast<int>(10)));
  (void)(x.inc(static_cast<long int>(10)));
  (void)(x.inc(delta));
  (void)(y.inc(10));
  (void)(y.inc(static_cast<int>(10)));
  (void)(y.inc(static_cast<long int>(10)));
  (void)(y.inc(delta));

  (void)(x.dec(10));
  (void)(x.dec(static_cast<int>(10)));
  (void)(x.dec(static_cast<long int>(10)));
  (void)(x.dec(delta));
}

#endif
