#include <iostream>
#include <memory>
#include <functional>

#include <dune/common/filledarray.hh>
#include <dune/grid/yaspgrid.hh>

#include <dune/functions/functionspacebases/compositebasis.hh>
#include <dune/functions/functionspacebases/lagrangebasis.hh>
#include <dune/functions/functionspacebases/powerbasis.hh>

#include <amdis/utility/TreeData.hpp>

#include "Tests.hpp"

using namespace AMDiS;


template <class Node>
using NodeData = double;

template <class Basis>
bool operator==(TreeData<Basis,NodeData,false> const& t1, TreeData<Basis,NodeData,false> const& t2)
{
  AMDIS_TEST(t1.basis() == t2.basis() && t1.basis() != nullptr);

  bool same = true;
  AMDiS::forEachNode_(t1.basis()->localView().tree(), [&](auto const& node, auto) {
    same = same && (t1[node] == t2[node]);
  });

  return same;
}

template <class Basis>
bool operator==(TreeData<Basis,NodeData,true> const& t1, TreeData<Basis,NodeData,true> const& t2)
{
  AMDIS_TEST(t1.basis() == t2.basis() && t1.basis() != nullptr);

  bool same = true;
  AMDiS::forEachLeafNode_(t1.basis()->localView().tree(), [&](auto const& node, auto) {
    same = same && (t1[node] == t2[node]);
  });

  return same;
}

int main ()
{
  using namespace Dune;
  using namespace Dune::Functions;
  using namespace Dune::Functions::BasisBuilder;

  FieldVector<double, 2> L; L = 1.0;
  auto s = Dune::filledArray<2>(1);
  YaspGrid<2> grid(L, s);
  auto gridView = grid.leafGridView();

  auto basis = makeBasis(
    gridView,
    composite(
      power<2>(lagrange<2>()),
      lagrange<1>()
    ));

  auto localView = basis.localView();
  auto const& tree = localView.tree();
  using Basis = std::remove_reference_t<decltype(basis)>;

  // test treeData for all nodes
  {
    // call default-constructor
    TreeData<Basis, NodeData, false> treeData;
    treeData.init(basis);

    AMDiS::forEachNode_(tree, [&](auto const& node, auto) {
      treeData[node] = double(node.treeIndex());
    });

    // call constructor with tree
    TreeData<Basis, NodeData, false> treeData1(basis);

    // call init on non-empty treeData
    treeData1.init(basis);

    // call copy-constructor
    TreeData<Basis, NodeData, false> treeData2(treeData);
    AMDIS_TEST(treeData == treeData2);

    // call copy-assignment operator on empty treeData
    TreeData<Basis, NodeData, false> treeData3;
    treeData3 = treeData;
    AMDIS_TEST(treeData == treeData3);

    // call copy-assignment operator on non-empty treeData
    treeData2 = treeData3;
    AMDIS_TEST(treeData3 == treeData2);

    // call move-assignment operator on non-empty treeData
    treeData = std::move(treeData2);

    // call move-constructor
    TreeData<Basis, NodeData, false> treeData4(std::move(treeData3));
  }

  // test treeData for leaf only
  {
    // call default-constructor
    TreeData<Basis, NodeData, true> treeData;
    treeData.init(basis);

    AMDiS::forEachLeafNode_(tree, [&](auto const& node, auto) {
      treeData[node] = double(node.treeIndex());
    });

    // call constructor with tree
    TreeData<Basis, NodeData, true> treeData1(basis);

    // call init on non-empty treeData
    treeData1.init(basis);

    // call copy-constructor
    TreeData<Basis, NodeData, true> treeData2(treeData);
    AMDIS_TEST(treeData == treeData2);

    // call copy-assignment operator on empty treeData
    TreeData<Basis, NodeData, true> treeData3;
    treeData3 = treeData;
    AMDIS_TEST(treeData == treeData3);

    // call copy-assignment operator on non-empty treeData
    treeData2 = treeData3;
    AMDIS_TEST(treeData3 == treeData2);

    // call move-assignment operator on non-empty treeData
    treeData = std::move(treeData2);

    // call move-constructor
    TreeData<Basis, NodeData, true> treeData4(std::move(treeData3));
  }

  // test for operations with uninitialized tree
  {
    // call default-constructor without initialization
    TreeData<Basis, NodeData, true> treeData;

    // call copy-constructor
    TreeData<Basis, NodeData, true> treeData2(treeData);

    // call move-constructor
    TreeData<Basis, NodeData, true> treeData3(std::move(treeData));
  }

  return report_errors();
}