//
// CPPINTS: A C++ Program to Generate Analytical Integrals Based on Gaussian
// Form Primitive Functions
//
// Copyright (C) 2015 The State University of New York at Buffalo
// This softare uses the MIT license as below:
//
//	Permission is hereby granted, free of charge, to any person obtaining
//	a copy of this software and associated documentation files (the "Software"),
//	to deal in the Software without restriction, including without limitation
//	the rights to use, copy, modify, merge, publish, distribute, sublicense,
//	and/or sell copies of the Software, and to permit persons to whom the Software
//	is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
//	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
//	PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
//	FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//
#include "basisutil.h"
#include "shellsymbol.h"
#include <boost/algorithm/string.hpp>
using namespace basisutil;
using namespace boost;

void BasisUtil::getLMNVal(const int& L, const int& index,
		int& l, int& m, int& n) const
{
	if (L<=MAX_L) {
		int ii = L*(L+1)*(L+2)/6 + index;
		l = LIBINT_BASIS_SET_ORDER[3*ii  ];
		m = LIBINT_BASIS_SET_ORDER[3*ii+1];
		n = LIBINT_BASIS_SET_ORDER[3*ii+2];
	}else{
		// now we have to use the libint order to generate th l m n
		int nx = -1;
		int ny = -1;
		int nz = -1;
		int count = 0;
		for(int x=0; x<=L; x++) {
			nx = L - x;
			for(int y=0; y<=x; y++) {
				ny = x-y;
				nz = y;
				if (count == index) {
					l = nx;
					m = ny;
					n = nz;
					return;
				}
				count++;
			}
		}
	}
}

int BasisUtil::getLocalBasisSetIndex(const int& l, const int& m,
		const int& n) const
{
	// actually we note that since here we calculate the local index
	// within the shell, therefore we only need the m and n value actually
	// however, we still pass in the l,m,n in full since we do not want
	// to mistake about whether it's l,m passed or m,n passed
	int index = -1;
	int x =  m + n;
	index = (x*(x+1))/2 + n;
	return index;
}


// ----------------------------------------------------------------------------
// -                        Open3D: www.open3d.org                            -
// ----------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2018 www.open3d.org
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// ----------------------------------------------------------------------------

#include "open3d/geometry/BoundingVolume.h"

#include <Eigen/Eigenvalues>
#include <numeric>

#include "open3d/geometry/PointCloud.h"
#include "open3d/geometry/Qhull.h"
#include "open3d/geometry/TriangleMesh.h"
#include "open3d/utility/Console.h"

namespace open3d {
namespace geometry {

OrientedBoundingBox& OrientedBoundingBox::Clear() {
    center_.setZero();
    extent_.setZero();
    R_ = Eigen::Matrix3d::Identity();
    color_.setZero();
    return *this;
}

bool OrientedBoundingBox::IsEmpty() const { return Volume() <= 0; }

Eigen::Vector3d OrientedBoundingBox::GetMinBound() const {
    auto points = GetBoxPoints();
    return ComputeMinBound(points);
}

Eigen::Vector3d OrientedBoundingBox::GetMaxBound() const {
    auto points = GetBoxPoints();
    return ComputeMaxBound(points);
}

Eigen::Vector3d OrientedBoundingBox::GetCenter() const { return center_; }

AxisAlignedBoundingBox OrientedBoundingBox::GetAxisAlignedBoundingBox() const {
    return AxisAlignedBoundingBox::CreateFromPoints(GetBoxPoints());
}

OrientedBoundingBox OrientedBoundingBox::GetOrientedBoundingBox() const {
    return *this;
}

OrientedBoundingBox& OrientedBoundingBox::Transform(
        const Eigen::Matrix4d& transformation) {
    utility::LogError(
            "A general transform of an OrientedBoundingBox is not implemented. "
            "Call Translate, Scale, and Rotate.");
    return *this;
}

OrientedBoundingBox& OrientedBoundingBox::Translate(
        const Eigen::Vector3d& translation, bool relative) {
    if (relative) {
        center_ += translation;
    } else {
        center_ = translation;
    }
    return *this;
}

OrientedBoundingBox& OrientedBoundingBox::Scale(const double scale,
                                                const Eigen::Vector3d& center) {
    extent_ *= scale;
    center_ = scale * (center_ - center) + center;
    return *this;
}

OrientedBoundingBox& OrientedBoundingBox::Rotate(
        const Eigen::Matrix3d& R, const Eigen::Vector3d& center) {
    R_ = R * R_;
    center_ = R * (center_ - center) + center;
    return *this;
}

double OrientedBoundingBox::Volume() const {
    return extent_(0) * extent_(1) * extent_(2);
}

std::vector<Eigen::Vector3d> OrientedBoundingBox::GetBoxPoints() const {
    Eigen::Vector3d x_axis = R_ * Eigen::Vector3d(extent_(0) / 2, 0, 0);
    Eigen::Vector3d y_axis = R_ * Eigen::Vector3d(0, extent_(1) / 2, 0);
    Eigen::Vector3d z_axis = R_ * Eigen::Vector3d(0, 0, extent_(2) / 2);
    std::vector<Eigen::Vector3d> points(8);
    points[0] = center_ - x_axis - y_axis - z_axis;
    points[1] = center_ + x_axis - y_axis - z_axis;
    points[2] = center_ - x_axis + y_axis - z_axis;
    points[3] = center_ - x_axis - y_axis + z_axis;
    points[4] = center_ + x_axis + y_axis + z_axis;
    points[5] = center_ - x_axis + y_axis + z_axis;
    points[6] = center_ + x_axis - y_axis + z_axis;
    points[7] = center_ + x_axis + y_axis - z_axis;
    return points;
}

std::vector<size_t> OrientedBoundingBox::GetPointIndicesWithinBoundingBox(
        const std::vector<Eigen::Vector3d>& points) const {
    std::vector<size_t> indices;
    Eigen::Vector3d dx = R_ * Eigen::Vector3d(1, 0, 0);
    Eigen::Vector3d dy = R_ * Eigen::Vector3d(0, 1, 0);
    Eigen::Vector3d dz = R_ * Eigen::Vector3d(0, 0, 1);
    for (size_t idx = 0; idx < points.size(); idx++) {
        Eigen::Vector3d d = points[idx] - center_;
        if (std::abs(d.dot(dx)) <= extent_(0) / 2 &&
            std::abs(d.dot(dy)) <= extent_(1) / 2 &&
            std::abs(d.dot(dz)) <= extent_(2) / 2) {
            indices.push_back(idx);
        }
    }
    return indices;
}

OrientedBoundingBox OrientedBoundingBox::CreateFromAxisAlignedBoundingBox(
        const AxisAlignedBoundingBox& aabox) {
    OrientedBoundingBox obox;
    obox.center_ = aabox.GetCenter();
    obox.extent_ = aabox.GetExtent();
    obox.R_ = Eigen::Matrix3d::Identity();
    return obox;
}

OrientedBoundingBox OrientedBoundingBox::CreateFromPoints(
        const std::vector<Eigen::Vector3d>& points) {
    PointCloud hull_pcd;
    hull_pcd.points_ = std::get<0>(Qhull::ComputeConvexHull(points))->vertices_;

    Eigen::Vector3d mean;
    Eigen::Matrix3d cov;
    std::tie(mean, cov) = hull_pcd.ComputeMeanAndCovariance();

    Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es(cov);
    Eigen::Vector3d evals = es.eigenvalues();
    Eigen::Matrix3d R = es.eigenvectors();
    R.col(0) /= R.col(0).norm();
    R.col(1) /= R.col(1).norm();
    R.col(2) /= R.col(2).norm();

    if (evals(1) > evals(0)) {
        std::swap(evals(1), evals(0));
        Eigen::Vector3d tmp = R.col(1);
        R.col(1) = R.col(0);
        R.col(0) = tmp;
    }
    if (evals(2) > evals(0)) {
        std::swap(evals(2), evals(0));
        Eigen::Vector3d tmp = R.col(2);
        R.col(2) = R.col(0);
        R.col(0) = tmp;
    }
    if (evals(2) > evals(1)) {
        std::swap(evals(2), evals(1));
        Eigen::Vector3d tmp = R.col(2);
        R.col(2) = R.col(1);
        R.col(1) = tmp;
    }

    for (auto& pt : hull_pcd.points_) {
        pt = R.transpose() * (pt - mean);
    }
    const auto aabox = hull_pcd.GetAxisAlignedBoundingBox();

    OrientedBoundingBox obox;
    obox.center_ = R * aabox.GetCenter() + mean;
    obox.R_ = R;
    obox.extent_ = aabox.GetExtent();

    return obox;
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::Clear() {
    min_bound_.setZero();
    max_bound_.setZero();
    return *this;
}

bool AxisAlignedBoundingBox::IsEmpty() const { return Volume() <= 0; }

Eigen::Vector3d AxisAlignedBoundingBox::GetMinBound() const {
    return min_bound_;
}

Eigen::Vector3d AxisAlignedBoundingBox::GetMaxBound() const {
    return max_bound_;
}

Eigen::Vector3d AxisAlignedBoundingBox::GetCenter() const {
    return (min_bound_ + max_bound_) * 0.5;
}

AxisAlignedBoundingBox AxisAlignedBoundingBox::GetAxisAlignedBoundingBox()
        const {
    return *this;
}

OrientedBoundingBox AxisAlignedBoundingBox::GetOrientedBoundingBox() const {
    return OrientedBoundingBox::CreateFromAxisAlignedBoundingBox(*this);
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::Transform(
        const Eigen::Matrix4d& transformation) {
    utility::LogError(
            "A general transform of a AxisAlignedBoundingBox would not be axis "
            "aligned anymore, convert it to a OrientedBoundingBox first");
    return *this;
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::Translate(
        const Eigen::Vector3d& translation, bool relative) {
    if (relative) {
        min_bound_ += translation;
        max_bound_ += translation;
    } else {
        const Eigen::Vector3d half_extent = GetHalfExtent();
        min_bound_ = translation - half_extent;
        max_bound_ = translation + half_extent;
    }
    return *this;
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::Scale(
        const double scale, const Eigen::Vector3d& center) {
    min_bound_ = center + scale * (min_bound_ - center);
    max_bound_ = center + scale * (max_bound_ - center);
    return *this;
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::Rotate(
        const Eigen::Matrix3d& rotation, const Eigen::Vector3d& center) {
    utility::LogError(
            "A rotation of a AxisAlignedBoundingBox would not be axis aligned "
            "anymore, convert it to an OrientedBoundingBox first");
    return *this;
}

std::string AxisAlignedBoundingBox::GetPrintInfo() const {
    return fmt::format("[({:.4f}, {:.4f}, {:.4f}) - ({:.4f}, {:.4f}, {:.4f})]",
                       min_bound_(0), min_bound_(1), min_bound_(2),
                       max_bound_(0), max_bound_(1), max_bound_(2));
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::operator+=(
        const AxisAlignedBoundingBox& other) {
    if (IsEmpty()) {
        min_bound_ = other.min_bound_;
        max_bound_ = other.max_bound_;
    } else if (!other.IsEmpty()) {
        min_bound_ = min_bound_.array().min(other.min_bound_.array()).matrix();
        max_bound_ = max_bound_.array().max(other.max_bound_.array()).matrix();
    }
    return *this;
}

AxisAlignedBoundingBox AxisAlignedBoundingBox::CreateFromPoints(
        const std::vector<Eigen::Vector3d>& points) {
    AxisAlignedBoundingBox box;
    if (points.empty()) {
        box.min_bound_ = Eigen::Vector3d(0.0, 0.0, 0.0);
        box.max_bound_ = Eigen::Vector3d(0.0, 0.0, 0.0);
    } else {
        box.min_bound_ = std::accumulate(
                points.begin(), points.end(), points[0],
                [](const Eigen::Vector3d& a, const Eigen::Vector3d& b) {
                    return a.array().min(b.array()).matrix();
                });
        box.max_bound_ = std::accumulate(
                points.begin(), points.end(), points[0],
                [](const Eigen::Vector3d& a, const Eigen::Vector3d& b) {
                    return a.array().max(b.array()).matrix();
                });
    }
    return box;
}

double AxisAlignedBoundingBox::Volume() const { return GetExtent().prod(); }

std::vector<Eigen::Vector3d> AxisAlignedBoundingBox::GetBoxPoints() const {
    std::vector<Eigen::Vector3d> points(8);
    Eigen::Vector3d extent = GetExtent();
    points[0] = min_bound_;
    points[1] = min_bound_ + Eigen::Vector3d(extent(0), 0, 0);
    points[2] = min_bound_ + Eigen::Vector3d(0, extent(1), 0);
    points[3] = min_bound_ + Eigen::Vector3d(0, 0, extent(2));
    points[4] = max_bound_;
    points[5] = max_bound_ - Eigen::Vector3d(extent(0), 0, 0);
    points[6] = max_bound_ - Eigen::Vector3d(0, extent(1), 0);
    points[7] = max_bound_ - Eigen::Vector3d(0, 0, extent(2));
    return points;
}

std::vector<size_t> AxisAlignedBoundingBox::GetPointIndicesWithinBoundingBox(
        const std::vector<Eigen::Vector3d>& points) const {
    std::vector<size_t> indices;
    for (size_t idx = 0; idx < points.size(); idx++) {
        const auto& point = points[idx];
        if (point(0) >= min_bound_(0) && point(0) <= max_bound_(0) &&
            point(1) >= min_bound_(1) && point(1) <= max_bound_(1) &&
            point(2) >= min_bound_(2) && point(2) <= max_bound_(2)) {
            indices.push_back(idx);
        }
    }
    return indices;
}

}  // namespace geometry
}  // namespace open3d


#ifndef D_MOSAIC_GEOCUTS_IMPL_HPP
#define D_MOSAIC_GEOCUTS_IMPL_HPP

#include <time.h>

#include <boost/config.hpp>
// for boost::tie
#include <boost/utility.hpp>
// for boost::graph_traits
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>

#include <boost/version.hpp>
#if BOOST_VERSION >= 104700
#include <boost/graph/boykov_kolmogorov_max_flow.hpp>
#elif BOOST_VERSION >= 103500
#include <boost/graph/kolmogorov_max_flow.hpp>
#endif

#include <vector>

static int debugOn = false;

#define SMIL_ENTER_FUNCTION(a)                                                 \
  {                                                                            \
    if (debugOn)                                                               \
      cout << "Entering function " << __func__ << " " << (a) << endl;          \
  }

#define SMIL_REGISTER_ERROR(a)

namespace smil
{
  /*
   * Some useful tools
   */
  /* This function selects points from an Structuring element
   * generating a positive offset
   */
  inline vector<IntPoint> filterStrElt(StrElt se)
  {
    vector<IntPoint> pts(0);

    vector<IntPoint>::iterator it, itStart, itEnd;
    itStart = se.points.begin();
    itEnd   = se.points.end();

    for (it = itStart; it != itEnd; it++) {
      bool ok = (4 * it->z + 2 * it->y + it->x) > 0;
      if (ok)
        pts.push_back(*it);

      if (debugOn) {
        std::cout << (ok ? "GOOD " : "BAD  ") << std::right << " "
                  << std::setw(6) << it->x << " " << std::setw(6) << it->y
                  << " " << std::setw(6) << it->z << endl;
      }
    }
    return pts;
  }

  /*
   *
   */
  using namespace boost;

  // needed for max flow: capacit map, rev_capacity map, etc.
  typedef adjacency_list_traits<vecS, vecS, directedS> Traits_T;

  typedef adjacency_list<
      vecS, vecS, directedS, property<vertex_name_t, std::string>,
      property<edge_capacity_t, double,
               property<edge_residual_capacity_t, double,
                        property<edge_reverse_t, Traits_T::edge_descriptor>>>>
      GraphVV_T;

  // edge capacity
  typedef property_map<GraphVV_T, edge_capacity_t>::type EdgeCapVV_T;
  // edge reverse
  typedef property_map<GraphVV_T, edge_reverse_t>::type EdgeRevCapVV_T;
  // edge residual capacity
  typedef property_map<GraphVV_T, edge_residual_capacity_t>::type EdgeResCapVV_T;
  //
  typedef property_map<GraphVV_T, vertex_index_t>::type VertexIndexVV_T;

  typedef adjacency_list<
      listS, vecS, directedS, property<vertex_name_t, std::string>,
      property<edge_capacity_t, double,
               property<edge_residual_capacity_t, double,
                        property<edge_reverse_t, Traits_T::edge_descriptor>>>>
      GraphLV_T;

  // edge capacity
  typedef property_map<GraphLV_T, edge_capacity_t>::type EdgeCapLV_T;
  // edge reverse
  typedef property_map<GraphLV_T, edge_reverse_t>::type EdgeRevCapLV_T;
  // edge residual capacity
  typedef property_map<GraphLV_T, edge_residual_capacity_t>::type EdgeResCapLV_T;
  //
  typedef property_map<GraphLV_T, vertex_index_t>::type VertexIndexLV_T;

  /*
   *
   *
   *
   *
   */
  template <class T>
  RES_T geoCutsMinSurfaces(const Image<T> &imIn, const Image<T> &imGrad,
                            const Image<T> &imMarker, const StrElt &nl,
                            Image<T> &imOut)
  {
    SMIL_ENTER_FUNCTION("");

    ASSERT_ALLOCATED(&imIn, &imGrad, &imMarker, &imOut);
    ASSERT_SAME_SIZE(&imIn, &imGrad, &imMarker, &imOut);

    typename Image<T>::lineType bufIn     = imIn.getPixels();
    typename Image<T>::lineType bufOut    = imOut.getPixels();
    typename Image<T>::lineType bufMarker = imMarker.getPixels();
    typename Image<T>::lineType bufGrad   = imGrad.getPixels();

    GraphVV_T g;
    EdgeCapVV_T capacity = boost::get(boost::edge_capacity, g);
    EdgeRevCapVV_T rev   = boost::get(boost::edge_reverse, g);
    EdgeResCapVV_T residual_capacity =
        boost::get(boost::edge_residual_capacity, g);

    GraphVV_T::edge_descriptor e1, e2, e3, e4, e5;
    GraphVV_T::vertex_descriptor vSource, vSink;

    int numVertex = maxVal(imIn);
    int numEdges  = 0;

    std::cout << "build Region Adjacency Graph" << std::endl;
    std::cout << "build Region Adjacency Graph Vertices" << std::endl;

    clock_t t1 = clock();

    std::cout << "number of Vertices : " << numVertex << std::endl;

    for (int i = 1; i <= numVertex; i++) {
      boost::add_vertex(g);
    }

    vSource = boost::add_vertex(g);
    vSink   = boost::add_vertex(g);

    clock_t tt_marker2 = 0, tt_marker3 = 0, tt_new_edge = 0, tt_old_edge = 0;
    clock_t t2 = clock();
    std::cout << "Nodes creation time : " << double(t2 - t1) / CLOCKS_PER_SEC
              << " seconds\n";

    std::cout << "Building Region Adjacency Graph Edges" << std::endl;
    t1 = clock();

    // iterators on the Structuring Element
    vector<IntPoint> pts = filterStrElt(nl);
    vector<IntPoint>::iterator itBegin, itEnd;
    itBegin = pts.begin();
    itEnd   = pts.end();

    int width  = imIn.getWidth();
    int height = imIn.getHeight();
    int depth  = imIn.getDepth();

    off_t strideY = width;
    off_t strideZ = width * height;
    for (int z = 0; z < depth; z++) {
      off_t p0Z = z * strideZ;
      for (int y = 0; y < height; y++) {
        off_t p0Y = y * strideY;
        for (int x = 0; x < width; x++) {
          off_t o1 = p0Z + p0Y + x;

          int val1        = (int) bufIn[o1];
          int marker      = (int) bufMarker[o1];
          int val_prec    = 0;
          int marker_prec = 0;

          if (val1 <= 0)
            continue;

          // do the following only for val1 > 0
          if (marker == 2 && marker_prec != marker && val_prec != val1) {
            clock_t temps_marker2 = clock();

            bool hasEdge;
            boost::tie(e4, hasEdge) = boost::edge(vSource, val1, g);
            if (!hasEdge) {
              // std::cout<<"Add new edge marker 2"<<std::endl;
              boost::tie(e4, hasEdge) = boost::add_edge(vSource, val1, g);
              boost::tie(e3, hasEdge) = boost::add_edge(val1, vSource, g);
              capacity[e4]            = (std::numeric_limits<double>::max)();
              capacity[e3]            = (std::numeric_limits<double>::max)();
              rev[e4]                 = e3;
              rev[e3]                 = e4;
            }
            tt_marker2 += clock() - temps_marker2;
          } else {
            if (marker == 3 && marker_prec != marker && val_prec != val1) {
              clock_t temps_marker3 = clock();
              bool hasEdge;
              boost::tie(e3, hasEdge) = boost::edge(vSink, val1, g);
              if (!hasEdge) {
                // std::cout<<"Add new edge marker 3"<<std::endl;
                boost::tie(e4, hasEdge) = boost::add_edge(val1, vSink, g);
                boost::tie(e3, hasEdge) = boost::add_edge(vSink, val1, g);
                capacity[e4]            = (std::numeric_limits<double>::max)();
                capacity[e3]            = (std::numeric_limits<double>::max)();
                rev[e4]                 = e3;
                rev[e3]                 = e4;
              }
              tt_marker3 += clock() - temps_marker3;
            }
          }

          double val_grad_o1 = (double) bufGrad[o1];
          // val de val2 precedente
          int val2_prec = val1;

          // iterators on the Structuring Element
          vector<IntPoint> pts = filterStrElt(nl);
          vector<IntPoint>::iterator itBegin, itEnd, it;
          itBegin = pts.begin();
          itEnd   = pts.end();

          for (it = itBegin; it != itEnd; it++) {
            if (x + it->x > width - 1 || x + it->x < 0)
              continue;
            if (y + it->y > height - 1 || y + it->y < 0)
              continue;
            if (z + it->z > depth - 1 || z + it->z < 0)
              continue;

            off_t o2 = o1 + it->z * strideZ + it->y * strideY + it->x;
            if (o2 <= o1)
              continue;

            int val2 = bufIn[o2];
            if (val1 == val2)
              continue;

            double val_grad_o2 = bufGrad[o2];
            double maxi        = std::max(val_grad_o1, val_grad_o2);
            double cost        = 10000.0 / (1.0 + std::pow(maxi, 4));

            if (val2_prec == val2) {
              // same val2 means same edge (thus, keep e3 and e4)
              capacity[e4] = capacity[e4] + cost;
              capacity[e3] = capacity[e3] + cost;
            } else {
              bool hasEdge;

              boost::tie(e5, hasEdge) = boost::edge(val1, val2, g);
              if (hasEdge == 0) {
                clock_t temps_new_edge = clock();
                // std::cout<<"Add new edge "<< val1<<" --
                // "<<val2<<std::endl;
                numEdges++;
                boost::tie(e4, hasEdge) = boost::add_edge(val1, val2, g);
                boost::tie(e3, hasEdge) = boost::add_edge(val2, val1, g);
                capacity[e4]            = cost;
                capacity[e3]            = cost;
                rev[e4]                 = e3;
                rev[e3]                 = e4;
                tt_new_edge += clock() - temps_new_edge;
              } else {
                clock_t temps_old_edge = clock();
                // std::cout<<"existing edge"<<std::endl;
                boost::tie(e4, hasEdge) = boost::edge(val1, val2, g);
                boost::tie(e3, hasEdge) = boost::edge(val2, val1, g);
                capacity[e4]            = capacity[e4] + cost;
                capacity[e3]            = capacity[e3] + cost;
                tt_old_edge += clock() - temps_old_edge;
              }
              val2_prec = val2;
            }
          }
          val_prec    = val1;
          marker_prec = marker;
        }
      }
    }

    std::cout << "Number of edges : " << numEdges << std::endl;
    t2 = clock();
    std::cout << "Edges creation time : " << double(t2 - t1) / CLOCKS_PER_SEC
              << " seconds\n";
    std::cout << "Marker2   : " << double(tt_marker2) / CLOCKS_PER_SEC
              << " seconds\n";
    std::cout << "Marker3   : " << double(tt_marker3) / CLOCKS_PER_SEC
              << " seconds\n";
    std::cout << "New edges : " << double(tt_new_edge) / CLOCKS_PER_SEC
              << " seconds\n";
    std::cout << "Old edges : " << double(tt_old_edge) / CLOCKS_PER_SEC
              << " seconds\n";

    boost::property_map<GraphVV_T, boost::vertex_index_t>::type indexmap =
        boost::get(boost::vertex_index, g);
    std::vector<boost::default_color_type> color(boost::num_vertices(g));

    std::cout << "Compute Max flow" << std::endl;
    t1 = clock();
#if BOOST_VERSION >= 104700
      // XXX - JOE - Check against this function prototype :
      // boykov_kolmogorov_max_flow(
      //     Graph& g,
      //     CapacityEdgeMap cap,
      //     ResidualCapacityEdgeMap res_cap,
      //     ReverseEdgeMap rev_map,
      //     PredecessorMap pre_map,
      //     ColorMap color,
      //     DistanceMap dist,
      //     IndexMap idx,
      //     typename graph_traits<Graph>::vertex_descriptor src,
      //     typename graph_traits<Graph>::vertex_descriptor sink)
    double flow =
        boykov_kolmogorov_max_flow(g, capacity, residual_capacity, rev,
                                   &color[0], indexmap, vSource, vSink);
#else
    double flow = kolmogorov_max_flow(g, capacity, residual_capacity, rev,
                                      &color[0], indexmap, vSource, vSink);
#endif
    std::cout << "c  The total flow:" << std::endl;
    std::cout << "s " << flow << std::endl << std::endl;
    t2 = clock();
    std::cout << "Flow computation time : " << double(t2 - t1) / CLOCKS_PER_SEC
              << " seconds\n";

    t1                = clock();
    size_t pixelCount = imIn.getPixelCount();
    for (off_t o1 = 0; o1 < (off_t) pixelCount; o1++) {
      int val = (T) bufIn[o1];

      if (val == 0) {
        bufOut[o1] = 0;
      } else {
        if (color[val] == color[vSource])
          bufOut[o1] = 2;
        else if (color[val] == color[vSink])
          bufOut[o1] = 3;
        else
          bufOut[o1] = 4;
      }
    }

    t2 = clock();
    std::cout << "Computing imOut took : " << double(t2 - t1) / CLOCKS_PER_SEC
              << " seconds\n";

    return RES_OK;
  }

  /*
   *
   *
   *
   *
   */
#if 0
  template <class ImageIn, class ImageGrad, class ImageMarker, class SE,
            class ImageOut>
  RES_T geoCutsMinSurfaces_with_Line(
      const ImageIn &imIn, const ImageGrad &imGrad, const ImageMarker &imMarker,
      const SE &nl, ImageOut &imOut)

  }
#endif

  /*
   *
   *
   *
   *
   */
#if 0
  // ImageLabel and ImageMarker should be unsigned integers
  template <class ImageLabel, class ImageVal, class ImageMarker, class SE,
            class ImageOut>
  RES_T geoCutsMinSurfaces_with_steps(
      const ImageLabel &imLabel, const ImageVal &imVal,
      const ImageMarker &imMarker, const SE &nl, F_SIMPLE step_x,
      F_SIMPLE step_y, F_SIMPLE step_z, ImageOut &imOut)
#endif

  /*
   *
   *
   *
   *
   */
#if 0
  // ImageLabel and ImageMarker should be unsigned integers
  template <class ImageLabel, class ImageVal, class ImageMarker, class SE,
            class ImageOut>
  RES_T geoCutsMinSurfaces_with_steps_vGradient(
      const ImageLabel &imLabel, const ImageVal &imVal,
      const ImageMarker &imMarker, const SE &nl, F_SIMPLE step_x,
      F_SIMPLE step_y, F_SIMPLE step_z, ImageOut &imOut)
#endif

  /*
   *
   *
   *
   *
   */
#if 0
  template <class ImageIn, class ImageGrad, class ImageMarker, class SE,
            class ImageOut>
  RES_T geoCutsMinSurfaces_with_steps_old(
      const ImageIn &imIn, const ImageGrad &imGrad, const ImageMarker &imMarker,
      const SE &nl, F_SIMPLE step_x, F_SIMPLE step_y, F_SIMPLE step_z,
      ImageOut &imOut)
#endif

  /*
   *
   *
   *
   *
   */
  template <class T>
  RES_T geoCutsMultiWay_MinSurfaces(const Image<T> &imIn,
                                     const Image<T> &imGrad,
                                     const Image<T> &imMarker, const StrElt &nl,
                                     Image<T> &imOut)
  {
    SMIL_ENTER_FUNCTION("");

    ASSERT_ALLOCATED(&imIn, &imGrad, &imMarker, &imOut);
    ASSERT_SAME_SIZE(&imIn, &imGrad, &imMarker, &imOut);

    typename Image<T>::lineType bufIn     = imIn.getPixels();
    typename Image<T>::lineType bufOut    = imOut.getPixels();
    typename Image<T>::lineType bufMarker = imMarker.getPixels();
    typename Image<T>::lineType bufGrad   = imGrad.getPixels();

    std::cout << "build Region Adjacency Graph" << std::endl;

    GraphLV_T g;
    EdgeCapLV_T capacity = boost::get(boost::edge_capacity, g);
    EdgeRevCapLV_T rev   = boost::get(boost::edge_reverse, g);
    EdgeResCapLV_T residual_capacity =
        boost::get(boost::edge_residual_capacity, g);

    GraphLV_T::edge_descriptor e1, e2, e3, e4;
    GraphLV_T::vertex_descriptor vSource, vSink;

    int numVertex = maxVal(imIn);
    int numLabels = maxVal(imMarker);

    size_t pixelCount = imIn.getPixelCount();

    std::cout << "number of labels :" << numLabels << std::endl;

    std::cout << "build Region Adjacency Graph Vertices" << std::endl;

    for (int i = 0; i <= numVertex; i++) {
      boost::add_vertex(g);
    }

    vSource = boost::add_vertex(g);
    vSink   = boost::add_vertex(g);

    std::vector<boost::default_color_type> color(boost::num_vertices(g));
    VertexIndexLV_T indexmap = boost::get(boost::vertex_index, g);

    std::cout << "build Region Adjacency Graph Edges" << std::endl;

    int width  = imIn.getWidth();
    int height = imIn.getHeight();
    int depth  = imIn.getDepth();

    off_t strideY = width;
    off_t strideZ = width * height;
    for (int z = 0; z < depth; z++) {
      off_t p0Z = z * strideZ;
      for (int y = 0; y < height; y++) {
        off_t p0Y = y * strideY;
        for (int x = 0; x < width; x++) {
          off_t o1 = p0Z + p0Y + x;
          int val1 = (int) bufIn[o1];
          // int marker = (int) bufMarker[o1]; // XXX unused ???

          // iterators on the Structuring Element
          vector<IntPoint> pts = filterStrElt(nl);
          vector<IntPoint>::iterator itBegin, itEnd, it;
          itBegin = pts.begin();
          itEnd   = pts.end();

          for (it = itBegin; it != itEnd; it++) {
            if (x + it->x > width - 1 || x + it->x < 0)
              continue;
            if (y + it->y > height - 1 || y + it->y < 0)
              continue;
            if (z + it->z > depth - 1 || z + it->z < 0)
              continue;

            off_t o2 = o1 + it->z * strideZ + it->y * strideY + it->x;
            if (o2 <= o1)
              continue;

            int val2 = bufIn[o2];
            if (val1 == val2)
              continue;

            bool hasEdge;
            boost::tie(e3, hasEdge) = boost::edge(val1, val2, g);
            // std::cout<<hasEdge<<std::endl;
            // std::cout<<"Compute Gradient"<<std::endl;
            double val3 = (double) bufGrad[o1];
            double val4 = (double) bufGrad[o2];
            double maxi = std::max(val3, val4);
            double cost = 10000.0 / (1.0 + std::pow(maxi, 4));

            if (!hasEdge) {
              // std::cout<<"Add new edge"<<std::endl;
              boost::tie(e4, hasEdge) = boost::add_edge(val1, val2, g);
              boost::tie(e3, hasEdge) = boost::add_edge(val2, val1, g);
              capacity[e4]            = cost;
              capacity[e3]            = cost;
              rev[e4]                 = e3;
              rev[e3]                 = e4;
            } else {
              // std::cout<<"existing edge"<<std::endl;
              boost::tie(e4, hasEdge) = boost::edge(val1, val2, g);
              boost::tie(e3, hasEdge) = boost::edge(val2, val1, g);
              capacity[e4]            = capacity[e4] + cost;
              capacity[e3]            = capacity[e3] + cost;
            }
          }
        }
      }
    }

    for (int nbk = 2; nbk <= numLabels; nbk++) {
      // for all pixels in imIn create a vertex
      for (off_t o0 = 0; o0 < (off_t) pixelCount; o0++) {
        int val1 = (int) bufMarker[o0];
        int val2 = (int) bufIn[o0];

        bool hasEdge;
        if (val1 == nbk) {
          boost::tie(e4, hasEdge) = boost::edge(vSource, val2, g);
          if (!hasEdge) {
            boost::tie(e4, hasEdge) = boost::add_edge(vSource, val2, g);
            boost::tie(e3, hasEdge) = boost::add_edge(val2, vSource, g);
            capacity[e4]            = (std::numeric_limits<double>::max)();
            capacity[e3]            = (std::numeric_limits<double>::max)();
            rev[e4]                 = e3;
            rev[e3]                 = e4;
          }
        } else if (val1 > 1 && val1 != nbk) {
          boost::tie(e4, hasEdge) = boost::edge(val2, vSink, g);
          if (!hasEdge) {
            boost::tie(e4, hasEdge) = boost::add_edge(val2, vSink, g);
            boost::tie(e3, hasEdge) = boost::add_edge(vSink, val2, g);
            capacity[e4]            = (std::numeric_limits<double>::max)();
            capacity[e3]            = (std::numeric_limits<double>::max)();
            rev[e4]                 = e3;
            rev[e3]                 = e4;
          }
        }
      }

      std::cout << "Compute Max flow" << nbk << std::endl;
#if BOOST_VERSION >= 104700
      // XXX - JOE - Check against this function prototype :
      // boykov_kolmogorov_max_flow(
      //     Graph& g,
      //     CapacityEdgeMap cap,
      //     ResidualCapacityEdgeMap res_cap,
      //     ReverseEdgeMap rev_map,
      //     PredecessorMap pre_map,
      //     ColorMap color,
      //     DistanceMap dist,
      //     IndexMap idx,
      //     typename graph_traits<Graph>::vertex_descriptor src,
      //     typename graph_traits<Graph>::vertex_descriptor sink)
      double flow =
          boykov_kolmogorov_max_flow(g, capacity, residual_capacity, rev,
                                     &color[0], indexmap, vSource, vSink);
#else
      double flow = kolmogorov_max_flow(g, capacity, residual_capacity, rev,
                                        &color[0], indexmap, vSource, vSink);
#endif

      std::cout << "c  The total flow:" << std::endl;
      std::cout << "s " << flow << std::endl << std::endl;

      // for all pixels in imIn create a vertex and an edge
      for (off_t o1 = 0; o1 < (off_t) pixelCount; o1++) {
        int val1 = (int) bufIn[o1];
        int val2 = (int) bufOut[o1];
        int val3 = (int) bufMarker[o1];

        if (val2 == 1) {
          if (color[val1] == color[vSource])
            bufOut[o1] = (T) nbk;
        }

        bool hasEdge;
        if (val3 == nbk) {
          boost::tie(e4, hasEdge) = boost::edge(vSource, val1, g);
          if (hasEdge) {
            boost::remove_edge(vSource, val1, g);
            boost::remove_edge(val1, vSource, g);
          }
        } else if (val3 > 1) {
          boost::tie(e4, hasEdge) = boost::edge(val1, vSink, g);
          if (hasEdge) {
            boost::remove_edge(val1, vSink, g);
            boost::remove_edge(vSink, val1, g);
          }
        }
      }
    }

    return RES_OK;
  }

  /*
   *
   *
   *
   *
   */
#if 0
  template <class ImageIn, class ImageGrad, class ImageMosaic,
            class ImageMarker, class SE, class ImageOut>
  RES_T geoCutsOptimize_Mosaic(
      const ImageIn &imIn, const ImageGrad &imGrad, const ImageMosaic &imMosaic,
      const ImageMarker &imMarker, const SE &nl, ImageOut &imOut)
#endif

  /*
   *
   *
   *
   *
   */
#if 0
  template <class ImageIn, class ImageGrad, class ImageCurvature,
            class ImageMarker, typename _Beta, class SE, class ImageOut>
  RES_T geoCutsRegularized_MinSurfaces(
      const ImageIn &imIn, const ImageGrad &imGrad,
      const ImageCurvature &imCurvature, const ImageMarker &imMarker,
      const _Beta Beta, const SE &nl, ImageOut &imOut)
#endif

  /*
   *
   *
   *
   *
   */
#if 0
  template <class ImageIn, class ImageMosaic, class ImageMarker, class SE,
            class ImageOut>
  RES_T geoCutsSegment_Graph(const ImageIn &imIn, const ImageMosaic &imMosaic,
                              const ImageMarker &imMarker, const SE &nl,
                              ImageOut &imOut)
#endif

  /*
   *
   *
   *
   *
   */
#if 0
  template <class ImageIn, class ImageMosaic, class ImageMarker, typename _Beta,
            typename _Sigma, class SE, class ImageOut>
  RES_T MAP_MRF_edge_preserving(
      const ImageIn &imIn, const ImageMosaic &imMosaic,
      const ImageMarker &imMarker, const _Beta Beta, const _Sigma Sigma,
      const SE &nl, ImageOut &imOut)
#endif

  /*
   *
   *
   *
   *
   */
#if 0
  template <class ImageIn, class ImageMosaic, class ImageMarker, typename _Beta,
            typename _Sigma, class SE, class ImageOut>
  RES_T MAP_MRF_Ising(const ImageIn &imIn, const ImageMosaic &imMosaic,
                      const ImageMarker &imMarker, const _Beta Beta,
                      const _Sigma Sigma, const SE &nl, ImageOut &imOut)
#endif

  /*
   *
   *
   *
   *
   */
#if 0
  template <class ImageIn, class ImageMosaic, class ImageMarker, typename _Beta,
            typename _Sigma, class SE, class ImageOut>
  RES_T MAP_MRF_Potts(const ImageIn &imIn, const ImageMosaic &imMosaic,
                      const ImageMarker &imMarker, const _Beta Beta,
                      const _Sigma Sigma, const SE &nl, ImageOut &imOut)
#endif

/*
 *
 *
 *
 *
 */
#define indent "   "

  inline void printSE(StrElt ss)
  {
    cout << indent << "Structuring Element" << endl;
    cout << indent << "Type: " << ss.seT << endl;
    cout << indent << "Size: " << ss.size << endl;
    size_t ptNbr = ss.points.size();
    cout << indent << "Point Nbr: " << ptNbr << endl;
    if (ptNbr == 0)
      return;

    vector<IntPoint>::iterator itStart = ss.points.begin();
    vector<IntPoint>::iterator it, itEnd;
    itEnd = ss.points.end();

    vector<IntPoint> z(0);
    z       = filterStrElt(ss);
    itStart = z.begin();
    itEnd   = z.end();
    for (it = itStart; it != itEnd; it++) {
      // cout << "z      " << "* " << it->x << " " << it->y << " " << it->z <<
      // endl;
    }
    cout << " =================== " << endl;
  }

  template <class T> void testHandleSE(const Image<T> &img, StrElt se)
  {
    StrElt sx = se;
    // sx = CubeSE(2);
    printSE(sx);
    sx = sx.noCenter();
    printSE(sx);
    int sz = sx.getSize();
    sx     = sx.homothety(sz);
    printSE(sx);

#if 1
    img.printSelf();
    cout << "* width " << img.getWidth() << endl;
    cout << "* height " << img.getHeight() << endl;
    cout << "* depth " << img.getDepth() << endl;
#endif
  }

} // namespace smil

#endif // D_MOSAIC_GEOCUTS_IMPL_HPP


//=======================================================================
// Copyright 2002 Indiana University.
// Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================

#ifndef BOOST_GRAPH_DAG_SHORTEST_PATHS_HPP
#define BOOST_GRAPH_DAG_SHORTEST_PATHS_HPP

#include <boost/graph/topological_sort.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>

// single-source shortest paths for a Directed Acyclic Graph (DAG)

namespace boost
{

// Initalize distances and call depth first search
template < class VertexListGraph, class DijkstraVisitor, class DistanceMap,
    class WeightMap, class ColorMap, class PredecessorMap, class Compare,
    class Combine, class DistInf, class DistZero >
inline void dag_shortest_paths(const VertexListGraph& g,
    typename graph_traits< VertexListGraph >::vertex_descriptor s,
    DistanceMap distance, WeightMap weight, ColorMap color, PredecessorMap pred,
    DijkstraVisitor vis, Compare compare, Combine combine, DistInf inf,
    DistZero zero)
{
    typedef typename graph_traits< VertexListGraph >::vertex_descriptor Vertex;
    std::vector< Vertex > rev_topo_order;
    rev_topo_order.reserve(num_vertices(g));

    // Call 'depth_first_visit', not 'topological_sort', because we don't
    // want to traverse the entire graph, only vertices reachable from 's',
    // and 'topological_sort' will traverse everything. The logic below
    // is the same as for 'topological_sort', only we call 'depth_first_visit'
    // and 'topological_sort' calls 'depth_first_search'.
    topo_sort_visitor< std::back_insert_iterator< std::vector< Vertex > > >
        topo_visitor(std::back_inserter(rev_topo_order));
    depth_first_visit(g, s, topo_visitor, color);

    typename graph_traits< VertexListGraph >::vertex_iterator ui, ui_end;
    for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui)
    {
        put(distance, *ui, inf);
        put(pred, *ui, *ui);
    }

    put(distance, s, zero);
    vis.discover_vertex(s, g);
    typename std::vector< Vertex >::reverse_iterator i;
    for (i = rev_topo_order.rbegin(); i != rev_topo_order.rend(); ++i)
    {
        Vertex u = *i;
        vis.examine_vertex(u, g);
        typename graph_traits< VertexListGraph >::out_edge_iterator e, e_end;
        for (boost::tie(e, e_end) = out_edges(u, g); e != e_end; ++e)
        {
            vis.discover_vertex(target(*e, g), g);
            bool decreased
                = relax(*e, g, weight, pred, distance, combine, compare);
            if (decreased)
                vis.edge_relaxed(*e, g);
            else
                vis.edge_not_relaxed(*e, g);
        }
        vis.finish_vertex(u, g);
    }
}

namespace detail
{

    // Defaults are the same as Dijkstra's algorithm

    // Handle Distance Compare, Combine, Inf and Zero defaults
    template < class VertexListGraph, class DijkstraVisitor, class DistanceMap,
        class WeightMap, class ColorMap, class IndexMap, class Params >
    inline void dag_sp_dispatch2(const VertexListGraph& g,
        typename graph_traits< VertexListGraph >::vertex_descriptor s,
        DistanceMap distance, WeightMap weight, ColorMap color, IndexMap /*id*/,
        DijkstraVisitor vis, const Params& params)
    {
        typedef typename property_traits< DistanceMap >::value_type D;
        dummy_property_map p_map;
        D inf = choose_param(get_param(params, distance_inf_t()),
            (std::numeric_limits< D >::max)());
        dag_shortest_paths(g, s, distance, weight, color,
            choose_param(get_param(params, vertex_predecessor), p_map), vis,
            choose_param(
                get_param(params, distance_compare_t()), std::less< D >()),
            choose_param(
                get_param(params, distance_combine_t()), closed_plus< D >(inf)),
            inf, choose_param(get_param(params, distance_zero_t()), D()));
    }

    // Handle DistanceMap and ColorMap defaults
    template < class VertexListGraph, class DijkstraVisitor, class DistanceMap,
        class WeightMap, class ColorMap, class IndexMap, class Params >
    inline void dag_sp_dispatch1(const VertexListGraph& g,
        typename graph_traits< VertexListGraph >::vertex_descriptor s,
        DistanceMap distance, WeightMap weight, ColorMap color, IndexMap id,
        DijkstraVisitor vis, const Params& params)
    {
        typedef typename property_traits< WeightMap >::value_type T;
        typename std::vector< T >::size_type n;
        n = is_default_param(distance) ? num_vertices(g) : 1;
        std::vector< T > distance_map(n);
        n = is_default_param(color) ? num_vertices(g) : 1;
        std::vector< default_color_type > color_map(n);

        dag_sp_dispatch2(g, s,
            choose_param(distance,
                make_iterator_property_map(
                    distance_map.begin(), id, distance_map[0])),
            weight,
            choose_param(color,
                make_iterator_property_map(
                    color_map.begin(), id, color_map[0])),
            id, vis, params);
    }

} // namespace detail

template < class VertexListGraph, class Param, class Tag, class Rest >
inline void dag_shortest_paths(const VertexListGraph& g,
    typename graph_traits< VertexListGraph >::vertex_descriptor s,
    const bgl_named_params< Param, Tag, Rest >& params)
{
    // assert that the graph is directed...
    null_visitor null_vis;
    detail::dag_sp_dispatch1(g, s, get_param(params, vertex_distance),
        choose_const_pmap(get_param(params, edge_weight), g, edge_weight),
        get_param(params, vertex_color),
        choose_const_pmap(get_param(params, vertex_index), g, vertex_index),
        choose_param(
            get_param(params, graph_visitor), make_dijkstra_visitor(null_vis)),
        params);
}

} // namespace boost

#endif // BOOST_GRAPH_DAG_SHORTEST_PATHS_HPP


// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <array>
#include <boost/functional/hash.hpp>
#include <cstdlib>
#include <iterator>
#include <limits>
#include <memory>
#include <pup.h>
#include <type_traits>
#include <unordered_map>
#include <utility>

#include "DataStructures/DataVector.hpp"
#include "DataStructures/Tags.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "Domain/Structure/Direction.hpp"
#include "Domain/Structure/DirectionMap.hpp"
#include "Domain/Structure/Element.hpp"
#include "Domain/Structure/ElementId.hpp"
#include "Domain/Structure/OrientationMap.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/MinmodHelpers.hpp"
#include "Evolution/DiscontinuousGalerkin/Limiters/MinmodType.hpp"
#include "NumericalAlgorithms/LinearOperators/MeanValue.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "Utilities/ErrorHandling/Assert.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/Literals.hpp"
#include "Utilities/Numeric.hpp"
#include "Utilities/TMPL.hpp"

namespace Limiters::Minmod_detail {

// This function combines the evaluation of the troubled-cell indicator with the
// computation of the post-limiter reduced slopes. The returned bool indicates
// whether the slopes are to be reduced. The slopes themselves are returned by
// pointer.
//
// Note: This function is only made available in this header file to facilitate
// testing.
template <size_t VolumeDim>
bool minmod_limited_slopes(
    gsl::not_null<DataVector*> u_lin_buffer,
    gsl::not_null<BufferWrapper<VolumeDim>*> buffer,
    gsl::not_null<double*> u_mean,
    gsl::not_null<std::array<double, VolumeDim>*> u_limited_slopes,
    Limiters::MinmodType minmod_type, double tvb_constant, const DataVector& u,
    const Mesh<VolumeDim>& mesh, const Element<VolumeDim>& element,
    const std::array<double, VolumeDim>& element_size,
    const DirectionMap<VolumeDim, double>& effective_neighbor_means,
    const DirectionMap<VolumeDim, double>& effective_neighbor_sizes) noexcept;

// Implements the minmod limiter for one Tensor<DataVector> at a time.
template <size_t VolumeDim, typename Tag, typename PackagedData>
bool minmod_impl(
    const gsl::not_null<DataVector*> u_lin_buffer,
    const gsl::not_null<BufferWrapper<VolumeDim>*> buffer,
    const gsl::not_null<typename Tag::type*> tensor,
    const Limiters::MinmodType minmod_type, const double tvb_constant,
    const Mesh<VolumeDim>& mesh, const Element<VolumeDim>& element,
    const tnsr::I<DataVector, VolumeDim, Frame::Logical>& logical_coords,
    const std::array<double, VolumeDim>& element_size,
    const std::unordered_map<
        std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>, PackagedData,
        boost::hash<std::pair<Direction<VolumeDim>, ElementId<VolumeDim>>>>&
        neighbor_data) noexcept {
  // True if the mesh is linear-order in every direction
  const bool mesh_is_linear = (mesh.extents() == Index<VolumeDim>(2));
  const bool minmod_type_is_linear =
      (minmod_type != Limiters::MinmodType::LambdaPiN);
  const bool using_linear_limiter_on_non_linear_mesh =
      minmod_type_is_linear and not mesh_is_linear;

  // In each direction, average the size of all different neighbors in that
  // direction. Note that only the component of neighor_size that is normal
  // to the face is needed (and, therefore, computed). Note that this average
  // does not depend on the solution on the neighboring elements, so could be
  // precomputed outside of `limit_one_tensor`. Changing the code to
  // precompute the average may or may not be a measurable optimization.
  const auto effective_neighbor_sizes =
      compute_effective_neighbor_sizes(element, neighbor_data);

  bool some_component_was_limited = false;
  for (size_t i = 0; i < tensor->size(); ++i) {
    // In each direction, average the mean of the i'th tensor component over
    // all different neighbors in that direction. This produces one effective
    // neighbor per direction.
    const auto effective_neighbor_means =
        compute_effective_neighbor_means<Tag>(i, element, neighbor_data);

    DataVector& u = (*tensor)[i];
    double u_mean = std::numeric_limits<double>::signaling_NaN();
    std::array<double, VolumeDim> u_limited_slopes{};
    const bool reduce_slopes = minmod_limited_slopes(
        u_lin_buffer, buffer, make_not_null(&u_mean),
        make_not_null(&u_limited_slopes), minmod_type, tvb_constant, u, mesh,
        element, element_size, effective_neighbor_means,
        effective_neighbor_sizes);

    if (reduce_slopes or using_linear_limiter_on_non_linear_mesh) {
      u = u_mean;
      for (size_t d = 0; d < VolumeDim; ++d) {
        u += logical_coords.get(d) * gsl::at(u_limited_slopes, d);
      }
      some_component_was_limited = true;
    }
  }  // end for loop over tensor components

  return some_component_was_limited;
}

}  // namespace Limiters::Minmod_detail