#include <utils/PugsAssert.hpp>

#include <mesh/Connectivity.hpp>
#include <mesh/ConnectivityToDiamondDualConnectivityDataMapper.hpp>
#include <mesh/DiamondDualConnectivityBuilder.hpp>
#include <mesh/DiamondDualConnectivityManager.hpp>
#include <utils/Exceptions.hpp>

#include <sstream>

DiamondDualConnectivityManager* DiamondDualConnectivityManager::m_instance{nullptr};

void
DiamondDualConnectivityManager::create()
{
  Assert(m_instance == nullptr, "DiamondDualConnectivityManager is already created");
  m_instance = new DiamondDualConnectivityManager;
}

void
DiamondDualConnectivityManager::destroy()
{
  Assert(m_instance != nullptr, "DiamondDualConnectivityManager was not created!");

  if (m_instance->m_connectivity_to_diamond_dual_connectivity_info_map.size() > 0) {
    std::stringstream error;
    error << ": some connectivities are still registered\n";
    for (const auto& [connectivity, diamond_dual_connectivity_info] :
         m_instance->m_connectivity_to_diamond_dual_connectivity_info_map) {
      error << " - connectivity " << rang::fgB::magenta << connectivity << rang::style::reset << '\n';
    }
    throw UnexpectedError(error.str());
  }
  delete m_instance;
  m_instance = nullptr;
}

void
DiamondDualConnectivityManager::deleteConnectivity(const IConnectivity* p_connectivity)
{
  m_connectivity_to_diamond_dual_connectivity_info_map.erase(p_connectivity);
}

DiamondDualConnectivityManager::DiamondDualConnectivityInfo
DiamondDualConnectivityManager::_getDiamondDualConnectivityInfo(const IConnectivity& connectivity)
{
  const IConnectivity* p_connectivity = &connectivity;

  if (auto i_connectivity = m_connectivity_to_diamond_dual_connectivity_info_map.find(p_connectivity);
      i_connectivity != m_connectivity_to_diamond_dual_connectivity_info_map.end()) {
    return i_connectivity->second;
  } else {
    DiamondDualConnectivityBuilder builder{connectivity};

    DiamondDualConnectivityInfo connectivity_info{builder.connectivity(), builder.mapper()};

    m_connectivity_to_diamond_dual_connectivity_info_map[p_connectivity] = connectivity_info;

    return connectivity_info;
  }
}

template <size_t Dimension>
std::shared_ptr<const Connectivity<Dimension>>
DiamondDualConnectivityManager::getDiamondDualConnectivity(const Connectivity<Dimension>& connectivity)
{
  return std::dynamic_pointer_cast<const Connectivity<Dimension>>(
    this->_getDiamondDualConnectivityInfo(connectivity).diamondDualConnectivity());
}

template <size_t Dimension>
std::shared_ptr<const ConnectivityToDiamondDualConnectivityDataMapper<Dimension>>
DiamondDualConnectivityManager::getConnectivityToDiamondDualConnectivityDataMapper(
  const Connectivity<Dimension>& connectivity)
{
  return std::dynamic_pointer_cast<const ConnectivityToDiamondDualConnectivityDataMapper<Dimension>>(
    this->_getDiamondDualConnectivityInfo(connectivity).connectivityToDiamondDualConnectivityDataMapper());
}

template std::shared_ptr<const Connectivity<1>> DiamondDualConnectivityManager::getDiamondDualConnectivity(
  const Connectivity<1>& connectivity);

template std::shared_ptr<const Connectivity<2>> DiamondDualConnectivityManager::getDiamondDualConnectivity(
  const Connectivity<2>& connectivity);

template std::shared_ptr<const Connectivity<3>> DiamondDualConnectivityManager::getDiamondDualConnectivity(
  const Connectivity<3>& connectivity);

template std::shared_ptr<const ConnectivityToDiamondDualConnectivityDataMapper<1>>
DiamondDualConnectivityManager::getConnectivityToDiamondDualConnectivityDataMapper(const Connectivity<1>&);

template std::shared_ptr<const ConnectivityToDiamondDualConnectivityDataMapper<2>>
DiamondDualConnectivityManager::getConnectivityToDiamondDualConnectivityDataMapper(const Connectivity<2>&);

template std::shared_ptr<const ConnectivityToDiamondDualConnectivityDataMapper<3>>
DiamondDualConnectivityManager::getConnectivityToDiamondDualConnectivityDataMapper(const Connectivity<3>&);