// Copyright (c) 2010-2022, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.

#include "mfem.hpp"
#include "unit_tests.hpp"

using namespace mfem;

TEST_CASE("Vector init-list construction", "[Vector]")
{
   double ContigData[6] = {6.0, 5.0, 4.0, 3.0, 2.0, 1.0};
   Vector a(ContigData, 6);
   Vector b({6.0, 5.0, 4.0, 3.0, 2.0, 1.0});

   for (int i = 0; i < a.Size(); i++)
   {
      REQUIRE(a(i) == b(i));
   }
}

TEST_CASE("Vector Move Constructor", "[Vector]")
{
   constexpr int N = 6;
   double ContigData[N] = {6.0, 5.0, 4.0, 3.0, 2.0, 1.0};
   Vector a(ContigData, N);
   Vector b(N);
   for (int i = 0; i < N; i++)
   {
      b(i) = N - i;
   }

   double* a_data = a.GetData();
   double* b_data = b.GetData();

   Vector move_non_owning(std::move(a));
   Vector move_owning(std::move(b));

   REQUIRE(a.Size() == 0);
   REQUIRE(a.GetData() == nullptr);
   REQUIRE(b.Size() == 0);
   REQUIRE(b.GetData() == nullptr);

   // Should both be no-ops
   a.Destroy();
   b.Destroy();

   REQUIRE(move_non_owning.OwnsData() == false);
   REQUIRE(move_owning.OwnsData() == true);

   REQUIRE(move_non_owning.Size() == N);
   REQUIRE(move_owning.Size() == N);

   // Make sure that the pointers were reused
   REQUIRE(move_non_owning.GetData() == a_data);
   REQUIRE(move_owning.GetData() == b_data);

   for (int i = 0; i < N; i++)
   {
      REQUIRE(move_non_owning(i) == N - i);
      REQUIRE(move_owning(i) == N - i);
   }
}

TEST_CASE("Vector Move Assignment", "[Vector]")
{
   constexpr int N = 6;
   double ContigData[N] = {6.0, 5.0, 4.0, 3.0, 2.0, 1.0};
   Vector a(ContigData, N);
   Vector b(N);
   for (int i = 0; i < N; i++)
   {
      b(i) = N - i;
   }

   double* a_data = a.GetData();
   double* b_data = b.GetData();

   Vector move_non_owning;
   move_non_owning = std::move(a);
   Vector move_owning;
   move_owning = std::move(b);

   REQUIRE(a.Size() == 0);
   REQUIRE(a.GetData() == nullptr);
   REQUIRE(b.Size() == 0);
   REQUIRE(b.GetData() == nullptr);

   // Should both be no-ops
   a.Destroy();
   b.Destroy();

   REQUIRE(move_non_owning.OwnsData() == false);
   REQUIRE(move_owning.OwnsData() == true);

   REQUIRE(move_non_owning.Size() == N);
   REQUIRE(move_owning.Size() == N);

   // Make sure that the pointers were reused
   REQUIRE(move_non_owning.GetData() == a_data);
   REQUIRE(move_owning.GetData() == b_data);

   for (int i = 0; i < N; i++)
   {
      REQUIRE(move_non_owning(i) == N - i);
      REQUIRE(move_owning(i) == N - i);
   }
}



TEST_CASE("Vector Tests", "[Vector]")
{
   double tol = 1e-12;

   Vector a(3),b(3);
   a(0) = 1.0;
   a(1) = 3.0;
   a(2) = 5.0;

   b(0) = 2.0;
   b(1) = 1.0;
   b(2) = 4.0;

   double bp[3];
   bp[0] = b(0);
   bp[1] = b(1);
   bp[2] = b(2);

   Vector apb(3), amb(3);
   apb(0) = 3.0;
   apb(1) = 4.0;
   apb(2) = 9.0;

   amb(0) = -1.0;
   amb(1) = 2.0;
   amb(2) = 1.0;

   Vector tmp(3);
   Vector diff(3);

   SECTION("Dot product")
   {
      REQUIRE(a*b  - 25.0 < tol);
      REQUIRE(a*bp - 25.0 < tol);
   }

   SECTION("Multiply and divide")
   {
      a *= 3.0;
      b /= -4.0;

      REQUIRE(a*b  + 3.0*25.0/4.0 < tol);
      REQUIRE(a*bp - 3.0*25.0     < tol);
   }

   SECTION("Minus scalar")
   {
      a -= 3.0;
      REQUIRE(a.Norml2() - sqrt(8.0) < tol);
   }

   SECTION("Minus vector")
   {
      a -= b;
      subtract(a, amb, diff);
      REQUIRE(diff.Norml2() < tol);
   }

   SECTION("Subtract vector")
   {
      subtract(0.5, a, b, tmp);
      tmp*= 2.0;
      subtract(tmp, amb, diff);
      REQUIRE(diff.Norml2() < tol);
   }

   SECTION("Plus scalar")
   {
      a += 2.0;
      REQUIRE(a.Norml2() - sqrt(83.0) < tol);
   }

   SECTION("Plus vector")
   {
      a += b;
      add(1.0, a, -1.0, apb, diff);
      REQUIRE(diff.Norml2() < tol);
   }

   SECTION("Add vector 1")
   {
      a.Add(1.0, b);
      add(1.0, a, -1.0, apb, diff);
      REQUIRE(diff.Norml2() < tol);
   }

   SECTION("Add vector 2")
   {
      add(a, b, tmp);
      apb.Neg();
      add(tmp, apb, diff);
      REQUIRE(diff.Norml2() < tol);
   }

   SECTION("Add vector 3")
   {
      add(a, 1.0, b, tmp);
      apb.Neg();
      add(tmp, apb, diff);
      REQUIRE(diff.Norml2() < tol);
   }

   SECTION("Add vector 4")
   {
      add(1.0, a, b, tmp);
      subtract(tmp, apb, diff);
      REQUIRE(diff.Norml2() < tol);
   }
}
