#include <scheme/DiscreteFunctionUtils.hpp>

#include <mesh/Connectivity.hpp>
#include <mesh/IMesh.hpp>
#include <mesh/Mesh.hpp>
#include <scheme/DiscreteFunctionP0.hpp>
#include <scheme/DiscreteFunctionVariant.hpp>
#include <utils/Stringify.hpp>

std::shared_ptr<const IMesh>
getCommonMesh(const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list)
{
  std::shared_ptr<const IMesh> i_mesh;
  bool is_same_mesh = true;
  for (const auto& discrete_function_variant : discrete_function_variant_list) {
    std::visit(
      [&](auto&& discrete_function) {
        if (not i_mesh.use_count()) {
          i_mesh = discrete_function.mesh();
        } else {
          if (i_mesh != discrete_function.mesh()) {
            is_same_mesh = false;
          }
        }
      },
      discrete_function_variant->discreteFunction());
  }
  if (not is_same_mesh) {
    i_mesh.reset();
  }
  return i_mesh;
}

bool
hasSameMesh(const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list)
{
  std::shared_ptr<const IMesh> i_mesh;
  bool is_same_mesh = true;
  for (const auto& discrete_function_variant : discrete_function_variant_list) {
    std::visit(
      [&](auto&& discrete_function) {
        if (not i_mesh.use_count()) {
          i_mesh = discrete_function.mesh();
        } else {
          if (i_mesh != discrete_function.mesh()) {
            is_same_mesh = false;
          }
        }
      },
      discrete_function_variant->discreteFunction());
  }

  return is_same_mesh;
}

template <size_t Dimension, typename DataType>
std::shared_ptr<const IDiscreteFunction>
shallowCopy(const std::shared_ptr<const Mesh<Connectivity<Dimension>>>& mesh,
            const std::shared_ptr<const DiscreteFunctionP0<Dimension, DataType>>& discrete_function)
{
  Assert(mesh->shared_connectivity() ==
           dynamic_cast<const Mesh<Connectivity<Dimension>>&>(*discrete_function->mesh()).shared_connectivity(),
         "connectivities should be the same");

  return std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh, discrete_function->cellValues());
}

template <typename MeshType, typename DiscreteFunctionT>
std::shared_ptr<const DiscreteFunctionVariant>
shallowCopy(const std::shared_ptr<const MeshType>& mesh, const DiscreteFunctionT& f)
{
  const std::shared_ptr function_mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());

  if (mesh->shared_connectivity() != function_mesh->shared_connectivity()) {
    throw NormalError("cannot shallow copy when connectivity changes");
  }

  if constexpr (std::is_same_v<MeshType, typename DiscreteFunctionT::MeshType>) {
    if constexpr (is_discrete_function_P0_v<DiscreteFunctionT>) {
      return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionT(mesh, f.cellValues()));
    } else if constexpr (is_discrete_function_P0_vector_v<DiscreteFunctionT>) {
      return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionT(mesh, f.cellArrays()));
    } else {
      throw UnexpectedError("invalid discrete function type");
    }
  } else {
    throw UnexpectedError("invalid mesh types");
  }
}

std::shared_ptr<const DiscreteFunctionVariant>
shallowCopy(const std::shared_ptr<const IMesh>& mesh,
            const std::shared_ptr<const DiscreteFunctionVariant>& discrete_function_variant)
{
  return std::visit(
    [&](auto&& f) {
      if (mesh == f.mesh()) {
        return discrete_function_variant;
      } else if (mesh->dimension() != f.mesh()->dimension()) {
        throw NormalError("incompatible mesh dimensions");
      }

      switch (mesh->dimension()) {
      case 1: {
        return shallowCopy(std::dynamic_pointer_cast<const Mesh<Connectivity<1>>>(mesh), f);
      }
      case 2: {
        return shallowCopy(std::dynamic_pointer_cast<const Mesh<Connectivity<2>>>(mesh), f);
      }
      case 3: {
        return shallowCopy(std::dynamic_pointer_cast<const Mesh<Connectivity<3>>>(mesh), f);
      }
        // LCOV_EXCL_START
      default: {
        throw UnexpectedError("invalid mesh dimension");
      }
        // LCOV_EXCL_STOP
      }
    },
    discrete_function_variant->discreteFunction());
}