#include"Mesh_Base.h"
#include"../common/util.h"
#include<algorithm>
#include<array>
#include<complex>
#include<iostream>
#include<cmath>
#include<vector>

//get edges with edge's flag of domain
void Mesh_2D::GetIndexOfDomain(Eigen::VectorXi domain, Eigen::VectorXi& nodeIndex, Eigen::VectorXi& edgeIndex)
{
	int NbrEdges = 0;

	int NbrDomain = domain.rows();
	for (int i = 0; i < NbrDomain; i++)
	{
		for (int j = 0; j < _mNbrEdges; j++)
		{
			if (GetDomainOfEdges(j) == domain(i))
				NbrEdges++;
		}
	}

	//store edgeIndex and nodeIndex
	Eigen::Vector2i coonOfEdges;
	int* tempEdgeIndex, * tempNodeIndex;
	tempEdgeIndex = new int[NbrEdges];
	tempNodeIndex = new int[NbrEdges * 2];
	int numNode = 0;
	int numEdge = 0;
	for (int i = 0; i < NbrDomain; i++)
	{
		for (int j = 0; j < _mNbrEdges; j++)
		{
			if (GetDomainOfEdges(j) == domain(i))
			{
				this->GetCoonOfEdges(j, coonOfEdges); //coonOfEdges 0:tri 1:numOfEdge
				tempEdgeIndex[numEdge] = GetEdgeOfTri(coonOfEdges(0), coonOfEdges(1));
				numEdge++;
				tempNodeIndex[numNode] = GetEdge(GetEdgeOfTri(coonOfEdges(0), coonOfEdges(1)), 0);
				numNode++;
				tempNodeIndex[numNode] = GetEdge(GetEdgeOfTri(coonOfEdges(0), coonOfEdges(1)), 1);
				numNode++;
			}
		}
	}



	//sort and unique nodeIndex
	std::sort(tempNodeIndex, tempNodeIndex + NbrEdges * 2);
	int NbrNodes = (std::unique(tempNodeIndex, tempNodeIndex + NbrEdges * 2) - tempNodeIndex);
	nodeIndex = Eigen::VectorXi::Zero(NbrNodes);
	for (int i = 0; i < NbrNodes; i++)
		nodeIndex(i) = tempNodeIndex[i];

	//sort edgeIndex
	edgeIndex = Eigen::VectorXi::Zero(NbrEdges);
	std::sort(tempEdgeIndex, tempEdgeIndex + NbrEdges);
	for (int i = 0; i < NbrEdges; i++)
		edgeIndex(i) = tempEdgeIndex[i];

	delete[] tempEdgeIndex, tempNodeIndex;


}

void Mesh_2D::GetIndexOfDomain2(Eigen::VectorXi domain, Eigen::VectorXi& edgeIndex, Eigen::VectorXi& edgeNum)
{
	int NbrEdges = 0;

	int NbrDomain = domain.rows();
	for (int i = 0; i < NbrDomain; i++)
	{
		for (int j = 0; j < _mNbrEdges; j++)
		{
			if (GetDomainOfEdges(j) == domain(i))
				NbrEdges++;
		}
	}

	//store edgeIndex
	edgeIndex = Eigen::VectorXi::Zero(NbrEdges);
	edgeNum = Eigen::VectorXi::Zero(NbrEdges);
	int numEdge = 0;
	for (int i = 0; i < NbrDomain; i++)
	{
		for (int j = 0; j < _mNbrEdges; j++)
		{
			if (GetDomainOfEdges(j) == domain(i))
			{
				edgeIndex(numEdge) = j;
				edgeNum(numEdge) = i;
				numEdge++;
			}
		}
	}

}

namespace {

void appendFaceEdgesOfTri(Mesh_3D* mesh, int triIdx, std::vector<int>& edges)
{
	Eigen::Vector3i conn;
	mesh->GetCoonOfTri(triIdx, conn);
	const int numTet = conn(0);
	const int numFace = conn(1) + 1;
	int e0 = 0, e1 = 0, e2 = 0;
	if (numFace == 1)
	{
		e0 = mesh->GetEdgeOfTet(numTet, 0);
		e1 = mesh->GetEdgeOfTet(numTet, 1);
		e2 = mesh->GetEdgeOfTet(numTet, 3);
	}
	else if (numFace == 2)
	{
		e0 = mesh->GetEdgeOfTet(numTet, 0);
		e1 = mesh->GetEdgeOfTet(numTet, 2);
		e2 = mesh->GetEdgeOfTet(numTet, 4);
	}
	else if (numFace == 3)
	{
		e0 = mesh->GetEdgeOfTet(numTet, 1);
		e1 = mesh->GetEdgeOfTet(numTet, 2);
		e2 = mesh->GetEdgeOfTet(numTet, 5);
	}
	else if (numFace == 4)
	{
		e0 = mesh->GetEdgeOfTet(numTet, 3);
		e1 = mesh->GetEdgeOfTet(numTet, 4);
		e2 = mesh->GetEdgeOfTet(numTet, 5);
	}
	else
	{
		return;
	}
	edges.push_back(e0);
	edges.push_back(e1);
	edges.push_back(e2);
}

void collectEdgesOnDomains(Mesh_3D* mesh, const Eigen::VectorXi& domains, std::vector<int>& edges)
{
	// Mirror MATLAB findTri + edge collect: gather/sort tris, then unique edges.
	std::vector<int> triIndex;
	for (int d = 0; d < domains.size(); d++)
	{
		const int domainId = domains(d);
		for (int j = 0; j < mesh->GetNbrTri(); j++)
		{
			if (mesh->GetDomainOfTri(j) == domainId)
				triIndex.push_back(j);
		}
	}
	std::sort(triIndex.begin(), triIndex.end());
	for (int j : triIndex)
		appendFaceEdgesOfTri(mesh, j, edges);
	std::sort(edges.begin(), edges.end());
	edges.erase(std::unique(edges.begin(), edges.end()), edges.end());
}

void getEdgeVertices(Mesh_3D* mesh, int edgeId, Eigen::Vector3d& p0, Eigen::Vector3d& p1)
{
	mesh->GetVertex(mesh->GetEdge(edgeId, 0), p0);
	mesh->GetVertex(mesh->GetEdge(edgeId, 1), p1);
}

bool matchEdgePair(Mesh_3D* mesh, int srcEdge, int dstEdge, double theta, double tol, int& signOut)
{
	Eigen::Vector3d v1, v2, v3, v4;
	getEdgeVertices(mesh, srcEdge, v1, v2);
	getEdgeVertices(mesh, dstEdge, v3, v4);

	Eigen::Matrix3d traMat;
	traMat << std::cos(theta), -std::sin(theta), 0.0,
		std::sin(theta), std::cos(theta), 0.0,
		0.0, 0.0, 1.0;
	v3 = traMat * v3;
	v4 = traMat * v4;

	const double l1 = (v1 - v3).norm();
	const double l2 = (v2 - v4).norm();
	const double l3 = (v1 - v4).norm();
	const double l4 = (v2 - v3).norm();
	if (l1 + l2 < tol)
	{
		signOut = 1;
		return true;
	}
	if (l3 + l4 < tol)
	{
		signOut = -1;
		return true;
	}
	return false;
}

// ---------------------------------------------------------------------------
// PBC pairing — mirrors MATLAB:
//   order1 translation:  matlab 3D一阶基+散射边界条件+周期边界/findPBCIndex.m
//   order2 faces:        matlab 3D二阶基+...+单周期边界/findPBCIndex.m (face
//                        branch), with translation instead of rotation.
// ---------------------------------------------------------------------------

static double matlabPbcTol(const Eigen::Vector3d& dis)
{
	// order1: err = norm(dis)*5e-5 (keep bit-identical for validated order1)
	const double dl = dis.norm();
	return (dl > 0.0) ? dl * 0.00005 : 0.01 * 0.00005;
}

static double matlabFacePbcTol(const Eigen::Vector3d& dis)
{
	// order2 face branch hardcodes dl=0.01 → err=5e-7. For µm translation
	// periods, norm(dis)*5e-5 is ~1e-10 and rejects true pairs; keep the
	// order2 floor while still scaling with larger periods.
	const double dl = dis.norm();
	const double rel = (dl > 0.0) ? dl * 0.00005 : 0.0;
	return std::max(rel, 0.01 * 0.00005);
}

bool matchEdgePairTranslation(Mesh_3D* mesh, int srcEdge, int dstEdge,
	const Eigen::Vector3d& dis, double tol, int& signOut)
{
	// MATLAB: l1=|‖v1−v3‖−dl|, l2=|‖v2−v4‖−dl|; flip uses (v1,v4)/(v2,v3).
	Eigen::Vector3d v1, v2, v3, v4;
	getEdgeVertices(mesh, srcEdge, v1, v2);
	getEdgeVertices(mesh, dstEdge, v3, v4);
	const double dl = dis.norm();
	const double l1 = std::abs((v1 - v3).norm() - dl);
	const double l2 = std::abs((v2 - v4).norm() - dl);
	const double l3 = std::abs((v1 - v4).norm() - dl);
	const double l4 = std::abs((v2 - v3).norm() - dl);
	if ((l1 + l2) < tol)
	{
		signOut = 1;
		return true;
	}
	if ((l3 + l4) < tol)
	{
		signOut = -1;
		return true;
	}
	return false;
}

void pairEdgesByTranslation(Mesh_3D* mesh,
	const std::vector<int>& srcEdges,
	const std::vector<int>& dstEdges,
	const Eigen::Vector3d& dis,
	std::vector<int>& pairedSrc,
	std::vector<int>& pairedDst,
	std::vector<int>& pairedSign,
	bool useFaceStyleTolFloor = false)
{
	const double tol = useFaceStyleTolFloor ? matlabFacePbcTol(dis) : matlabPbcTol(dis);

	if (!useFaceStyleTolFloor)
	{
		// Order-1: MATLAB first-match, no destination locking (bit-identical).
		for (int srcEdge : srcEdges)
		{
			bool matched = false;
			for (int dstEdge : dstEdges)
			{
				int sign = 1;
				if (matchEdgePairTranslation(mesh, srcEdge, dstEdge, dis, tol, sign))
				{
					pairedSrc.push_back(srcEdge);
					pairedDst.push_back(dstEdge);
					pairedSign.push_back(sign);
					matched = true;
					break;
				}
			}
			if (!matched)
			{
				pairedSrc.push_back(srcEdge);
				pairedDst.push_back(srcEdge);
				pairedSign.push_back(1);
			}
		}
		return;
	}

	// Order-2: first-match with loose tol, then drop duplicate dst (keep first).
	// Pure first-match without dedup breaks projection (duplicate dst).
	std::vector<int> srcU = srcEdges;
	std::vector<int> dstU = dstEdges;
	std::sort(srcU.begin(), srcU.end());
	srcU.erase(std::unique(srcU.begin(), srcU.end()), srcU.end());
	std::sort(dstU.begin(), dstU.end());
	dstU.erase(std::unique(dstU.begin(), dstU.end()), dstU.end());

	std::vector<char> dstUsed(static_cast<size_t>(dstU.size()), 0);
	for (int srcEdge : srcU)
	{
		bool matched = false;
		for (size_t dj = 0; dj < dstU.size(); dj++)
		{
			if (dstUsed[dj])
				continue;
			int sign = 1;
			if (!matchEdgePairTranslation(mesh, srcEdge, dstU[dj], dis, tol, sign))
				continue;
			pairedSrc.push_back(srcEdge);
			pairedDst.push_back(dstU[dj]);
			pairedSign.push_back(sign);
			dstUsed[dj] = 1;
			matched = true;
			break;
		}
		if (!matched)
		{
			pairedSrc.push_back(srcEdge);
			pairedDst.push_back(srcEdge);
			pairedSign.push_back(1);
		}
	}
	return;
}

void pairEdgesByGeometry(Mesh_3D* mesh,
	const std::vector<int>& srcEdges,
	const std::vector<int>& dstEdges,
	double theta,
	std::vector<int>& pairedSrc,
	std::vector<int>& pairedDst,
	std::vector<int>& pairedSign)
{
	// MATLAB rotation PBC: err = 0.01*5e-5, first match, no dst lock.
	const double tol = 0.01 * 0.00005;
	for (int srcEdge : srcEdges)
	{
		bool matched = false;
		for (int dstEdge : dstEdges)
		{
			int sign = 1;
			if (matchEdgePair(mesh, srcEdge, dstEdge, theta, tol, sign))
			{
				pairedSrc.push_back(srcEdge);
				pairedDst.push_back(dstEdge);
				pairedSign.push_back(sign);
				matched = true;
				break;
			}
		}
		if (!matched)
		{
			pairedSrc.push_back(srcEdge);
			pairedDst.push_back(srcEdge);
			pairedSign.push_back(1);
		}
	}
}

void getFaceCentroid(Mesh_3D* mesh, int faceId, Eigen::Vector3d& centroid)
{
	Eigen::Vector3d v0, v1, v2;
	mesh->GetVertex(mesh->GetFace(faceId, 0), v0);
	mesh->GetVertex(mesh->GetFace(faceId, 1), v1);
	mesh->GetVertex(mesh->GetFace(faceId, 2), v2);
	centroid = (v0 + v1 + v2) / 3.0;
}

void collectFacesOnDomains(Mesh_3D* mesh, const Eigen::VectorXi& domains, std::vector<int>& faces)
{
	// Mirror MATLAB findTri: gather tris on all domains, sort, then FaceOfTet.
	std::vector<int> triIndex;
	for (int d = 0; d < domains.size(); d++)
	{
		const int domainId = domains(d);
		for (int j = 0; j < mesh->GetNbrTri(); j++)
		{
			if (mesh->GetDomainOfTri(j) == domainId)
				triIndex.push_back(j);
		}
	}
	std::sort(triIndex.begin(), triIndex.end());
	faces.clear();
	faces.reserve(triIndex.size());
	for (int j : triIndex)
	{
		Eigen::Vector3i conn;
		mesh->GetCoonOfTri(j, conn);
		faces.push_back(mesh->GetFaceOfTet(conn(0), conn(1)));
	}
}

bool matchFacePair(Mesh_3D* mesh, int srcFace, int dstFace, double theta, double tol)
{
	// MATLAB order2 face branch (rotation): ‖c_src − R·c_dst‖ < err, sign = +1.
	Eigen::Vector3d c1, c2;
	getFaceCentroid(mesh, srcFace, c1);
	getFaceCentroid(mesh, dstFace, c2);
	Eigen::Matrix3d traMat;
	traMat << std::cos(theta), -std::sin(theta), 0.0,
		std::sin(theta), std::cos(theta), 0.0,
		0.0, 0.0, 1.0;
	c2 = traMat * c2;
	return (c1 - c2).norm() < tol;
}

bool matchFacePairTranslation(Mesh_3D* mesh, int srcFace, int dstFace,
	const Eigen::Vector3d& dis, double tol)
{
	// Translation port of MATLAB face branch: transform dst by −dis (src+dis=dst).
	Eigen::Vector3d c1, c2;
	getFaceCentroid(mesh, srcFace, c1);
	getFaceCentroid(mesh, dstFace, c2);
	return (c1 - (c2 - dis)).norm() < tol;
}

void pairFacesByTranslation(Mesh_3D* mesh,
	const std::vector<int>& srcFaces,
	const std::vector<int>& dstFaces,
	const Eigen::Vector3d& dis,
	std::vector<int>& pairedSrc,
	std::vector<int>& pairedDst,
	std::vector<int>& pairedSign)
{
	// Face branch of MATLAB findPBCIndex (translation form of ‖c_src−T(c_dst)‖).
	// MATLAB uses first-match without dst-lock; that yields duplicate dst rows and
	// breaks buildPeriodicProjection* (assumes unique sorted dst). We unique face
	// ids, then assign globally best residual first with exclusive dst — same
	// geometric criterion, projection-safe.
	std::vector<int> srcU = srcFaces;
	std::vector<int> dstU = dstFaces;
	std::sort(srcU.begin(), srcU.end());
	srcU.erase(std::unique(srcU.begin(), srcU.end()), srcU.end());
	std::sort(dstU.begin(), dstU.end());
	dstU.erase(std::unique(dstU.begin(), dstU.end()), dstU.end());

	const double tol = matlabFacePbcTol(dis);
	const int nSrc = static_cast<int>(srcU.size());
	const int nDst = static_cast<int>(dstU.size());

	struct Cand { double err; int si; int di; };
	std::vector<Cand> cands;
	cands.reserve(static_cast<size_t>(nSrc));
	for (int si = 0; si < nSrc; si++)
	{
		Eigen::Vector3d c1;
		getFaceCentroid(mesh, srcU[static_cast<size_t>(si)], c1);
		for (int di = 0; di < nDst; di++)
		{
			Eigen::Vector3d c2;
			getFaceCentroid(mesh, dstU[static_cast<size_t>(di)], c2);
			const double err = (c1 - (c2 - dis)).norm();
			if (err < tol)
				cands.push_back({ err, si, di });
		}
	}
	std::sort(cands.begin(), cands.end(),
		[](const Cand& a, const Cand& b) { return a.err < b.err; });

	std::vector<int> matchDst(static_cast<size_t>(nSrc), -1);
	std::vector<char> srcUsed(static_cast<size_t>(nSrc), 0);
	std::vector<char> dstUsed(static_cast<size_t>(nDst), 0);
	for (const Cand& c : cands)
	{
		if (srcUsed[static_cast<size_t>(c.si)] || dstUsed[static_cast<size_t>(c.di)])
			continue;
		srcUsed[static_cast<size_t>(c.si)] = 1;
		dstUsed[static_cast<size_t>(c.di)] = 1;
		matchDst[static_cast<size_t>(c.si)] = c.di;
	}

	for (int si = 0; si < nSrc; si++)
	{
		pairedSrc.push_back(srcU[static_cast<size_t>(si)]);
		if (matchDst[static_cast<size_t>(si)] >= 0)
			pairedDst.push_back(dstU[static_cast<size_t>(matchDst[static_cast<size_t>(si)])]);
		else
			pairedDst.push_back(srcU[static_cast<size_t>(si)]);
		pairedSign.push_back(1);
	}
}

void pairFacesByGeometry(Mesh_3D* mesh,
	const std::vector<int>& srcFaces,
	const std::vector<int>& dstFaces,
	double theta,
	std::vector<int>& pairedSrc,
	std::vector<int>& pairedDst,
	std::vector<int>& pairedSign)
{
	const double tol = 0.01 * 0.00005;
	for (int srcFace : srcFaces)
	{
		bool matched = false;
		for (int dstFace : dstFaces)
		{
			if (matchFacePair(mesh, srcFace, dstFace, theta, tol))
			{
				pairedSrc.push_back(srcFace);
				pairedDst.push_back(dstFace);
				pairedSign.push_back(1);
				matched = true;
				break;
			}
		}
		if (!matched)
		{
			pairedSrc.push_back(srcFace);
			pairedDst.push_back(srcFace);
			pairedSign.push_back(1);
		}
	}
}

void removeSelfPairs(std::vector<int>& srcEdges,
	std::vector<int>& dstEdges,
	std::vector<std::complex<double>>& phis)
{
	std::vector<int> newSrc, newDst;
	std::vector<std::complex<double>> newPhi;
	for (size_t i = 0; i < srcEdges.size(); i++)
	{
		if (srcEdges[i] == dstEdges[i])
			continue;
		newSrc.push_back(srcEdges[i]);
		newDst.push_back(dstEdges[i]);
		newPhi.push_back(phis[i]);
	}
	srcEdges.swap(newSrc);
	dstEdges.swap(newDst);
	phis.swap(newPhi);
}

} // namespace

void Mesh_2D::GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
	Eigen::VectorXi& srcNodeIndex, Eigen::VectorXi& dstNodeIndex, Eigen::VectorXcd& nodePhi,
	Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi)
{
	int NbrEdges = 0;

	int NbrDomain = PBC.rows();
	for (int i = 0; i < NbrDomain; i++)
	{
		for (int j = 0; j < _mNbrEdges; j++)
		{
			if (GetDomainOfEdges(j)-1 == PBC(i, 0))
				NbrEdges++;
		}
	}
	//store srcEdgeIndex and srcNodeIndex
	Eigen::Vector2i coonOfEdges;
	int* tempSrcEdgeIndex, * tempSrcNodeIndex;
	tempSrcEdgeIndex = new int[NbrEdges];
	tempSrcNodeIndex = new int[NbrEdges * 2];
	std::complex<double>* tempEdgePhi, * tempNodePhi;
	tempEdgePhi = new std::complex<double>[NbrEdges];
	tempNodePhi = new std::complex<double>[NbrEdges * 2];
	int numNode = 0;
	int numEdge = 0;
	int Tage_numEdge = 0;
	for (int i = 0; i < NbrDomain; i++)
	{
		for (int j = 0; j < _mNbrEdges; j++)
		{
			if (GetDomainOfEdges(j) - 1 == PBC(i, 0))
			{
				tempSrcEdgeIndex[numEdge] = j;
				tempEdgePhi[numEdge] = PBCData(i);
				numEdge++;
				this->GetCoonOfEdges(j, coonOfEdges);
				Tage_numEdge = GetEdgeOfTri(coonOfEdges(0), coonOfEdges(1));
				tempSrcNodeIndex[numNode] = GetEdge(Tage_numEdge, 0);
				tempNodePhi[numNode] = PBCData(i);
				numNode++;
				tempSrcNodeIndex[numNode] = GetEdge(Tage_numEdge, 1);
				tempNodePhi[numNode] = PBCData(i);
				numNode++;
			}
		}
	}
	//store dstEdgeIndex and dstNodeIndex
	int* tempDstEdgeIndex, * tempDstNodeIndex;
	tempDstEdgeIndex = new int[NbrEdges];
	tempDstNodeIndex = new int[NbrEdges * 2];
	for (int i = 0; i < NbrEdges; i++)
	{
		for (int j = 0; j < _mCopyOfEdges.rows(); j++)
		{
			if (tempSrcEdgeIndex[i] == _mCopyOfEdges(j, 0))
			{
				tempDstEdgeIndex[i] = _mCopyOfEdges(j, 1);
				break;
			}

		}
	}
	for (int i = 0; i < NbrEdges; i++)
	{
		this->GetCoonOfEdges(tempDstEdgeIndex[i], coonOfEdges);
		Tage_numEdge = GetEdgeOfTri(coonOfEdges(0), coonOfEdges(1));
		tempDstNodeIndex[i * 2] = GetEdge(Tage_numEdge, 0);
		tempDstNodeIndex[i * 2 + 1] = GetEdge(Tage_numEdge, 1);
	}
	//????????? DstNodeIndex ?? edgePhi
	//??????rotational bloch bounadry
	for (int i = 0; i < NbrEdges; i++)
	{
		for (int j = 0; j < _mCopyOfVertex.rows(); j++)
		{
			if (tempSrcNodeIndex[2 * i] == _mCopyOfVertex(j, 0) && tempDstNodeIndex[2 * i + 1] == _mCopyOfVertex(j, 1))
			{
				int temp = tempDstNodeIndex[i * 2];
				tempDstNodeIndex[i * 2] = tempDstNodeIndex[i * 2 + 1];
				tempDstNodeIndex[i * 2 + 1] = temp;
                         tempEdgePhi[i] = - tempEdgePhi[i];
			}
		}
	}
	srcEdgeIndex = Eigen::VectorXi::Zero(NbrEdges);
	dstEdgeIndex = Eigen::VectorXi::Zero(NbrEdges);
	edgePhi = Eigen::VectorXcd::Zero(NbrEdges);
	for (int i = 0; i < NbrEdges; i++)
	{
		this->GetCoonOfEdges(tempSrcEdgeIndex[i], coonOfEdges);
		Tage_numEdge = GetEdgeOfTri(coonOfEdges(0), coonOfEdges(1));
		srcEdgeIndex(i) = Tage_numEdge;
		this->GetCoonOfEdges(tempDstEdgeIndex[i], coonOfEdges);
		Tage_numEdge = GetEdgeOfTri(coonOfEdges(0), coonOfEdges(1));
		dstEdgeIndex(i) = Tage_numEdge;
		edgePhi(i) = tempEdgePhi[i];
	}
	//???????
	Eigen::VectorXi overlapIndex1, overlapIndex2;
	overlapIndex1 = Eigen::VectorXi::Zero(NbrEdges * 2);
	overlapIndex2 = Eigen::VectorXi::Zero(NbrEdges * 2);
	int Nbr_overlap;
	Nbr_overlap = 0;
	for (int i = 0; i < NbrEdges * 2; i++)
	{
		for (int j = i + 1; j < NbrEdges * 2; j++)
		{

			if (tempSrcNodeIndex[i] == tempSrcNodeIndex[j])
			{
				if (tempDstNodeIndex[i] == tempDstNodeIndex[j])
				{
					overlapIndex1[Nbr_overlap] = i;
					overlapIndex2[Nbr_overlap] = j;
					Nbr_overlap = Nbr_overlap + 1;
					break;
				}
			}

		}
	}
	overlapIndex2.conservativeResize(Nbr_overlap);
	int tempNum = 0;
	srcNodeIndex = Eigen::VectorXi::Zero(NbrEdges * 2 - Nbr_overlap);
	dstNodeIndex = Eigen::VectorXi::Zero(NbrEdges * 2 - Nbr_overlap);
	nodePhi = Eigen::VectorXcd::Zero(NbrEdges * 2 - Nbr_overlap);
	for (int i = 0; i < NbrEdges * 2; i++)
	{
		if ((overlapIndex2.array() == i).any())
		{
			tempNum++;
			continue;
		}
		srcNodeIndex(i - tempNum) = tempSrcNodeIndex[i];
		dstNodeIndex(i - tempNum) = tempDstNodeIndex[i];
		nodePhi(i - tempNum) = tempNodePhi[i];
	}
	srcNodeIndex.conservativeResize(NbrEdges * 2 - Nbr_overlap);
	dstNodeIndex.conservativeResize(NbrEdges * 2 - Nbr_overlap);
	int blochIndex, blochtemp = -1;
	for (int i = 0; i < NbrEdges * 2 - Nbr_overlap; i++)
	{
		for (int j = i + 1; j < NbrEdges * 2 - Nbr_overlap; j++)
		{
			if (srcNodeIndex(i) == srcNodeIndex(j))
			{
				blochtemp = srcNodeIndex[i];
			}
		}
	}
	for (int i = 0; i < NbrEdges * 2 - Nbr_overlap; i++)
	{
		for (int j = i + 1; j < NbrEdges * 2 - Nbr_overlap; j++)
		{
			if (dstNodeIndex(i) == dstNodeIndex(j))
			{
				srcNodeIndex(i) = blochtemp;
				nodePhi(i) = nodePhi(i) * nodePhi(j);
				if (j != srcNodeIndex.size() - 1)
				{
					srcNodeIndex.segment(j, NbrEdges * 2 - Nbr_overlap - j - 1) = srcNodeIndex.segment(j + 1, NbrEdges * 2 - Nbr_overlap - j - 1);
					dstNodeIndex.segment(j, NbrEdges * 2 - Nbr_overlap - j - 1) = dstNodeIndex.segment(j + 1, NbrEdges * 2 - Nbr_overlap - j - 1);
				}
			}
		}
	}
	if (blochtemp != -1)
	{
		srcNodeIndex.conservativeResize(NbrEdges * 2 - Nbr_overlap - 1);
		dstNodeIndex.conservativeResize(NbrEdges * 2 - Nbr_overlap - 1);
	}
	delete[] tempSrcEdgeIndex, tempSrcNodeIndex, tempEdgePhi, tempNodePhi, tempDstEdgeIndex, tempDstNodeIndex;
}

void Mesh_2D::GetEdgesIndexOfDomain(int domain, Eigen::VectorXi& edgesIndex)
{
	int NbrEdgesIndex = 0;
	for (int j = 0; j < _mNbrEdges; j++)
	{
		if (GetDomainOfEdges(j) == domain)
			NbrEdgesIndex++;
	}

	edgesIndex = Eigen::VectorXi::Zero(NbrEdgesIndex);
	int NumEdgesIndex = 0;
	for (int j = 0; j < _mNbrEdges; j++)
	{
		if (GetDomainOfEdges(j) == domain)
		{
			edgesIndex(NumEdgesIndex) = j;
			NumEdgesIndex++;
		}
	}
}

void Mesh_3D::GetTriIndexOfDomain(Eigen::VectorXi domain, Eigen::VectorXi& edgeIndex)
{
	int NbrTri = 0;

	int NbrDomain = domain.rows();
	for (int i = 0; i < NbrDomain; i++)
	{
		for (int j = 0; j < _mNbrTri; j++)
		{
			if (GetDomainOfTri(j) == domain(i))
				NbrTri++;
		}
	}

	//store edgeIndex
	Eigen::Vector3i coonOfTri;
	int* tempEdgeIndex;
	tempEdgeIndex = new int[NbrTri * 3];
	int numTri = 0;
	for (int i = 0; i < NbrDomain; i++)
	{
		for (int j = 0; j < _mNbrTri; j++)
		{
			if (GetDomainOfTri(j) == domain(i))
			{
				Eigen::Vector3i connOfTri;
				this->GetCoonOfTri(j, connOfTri);
				if (connOfTri(0) == 0)
				{
					if (connOfTri(2) == 0)
					{
						tempEdgeIndex[numTri * 3] = this->GetEdgeOfTet(connOfTri(1), 0);
						tempEdgeIndex[numTri * 3 + 1] = this->GetEdgeOfTet(connOfTri(1), 1);
						tempEdgeIndex[numTri * 3 + 2] = this->GetEdgeOfTet(connOfTri(1), 2);
					}
					else if (connOfTri(2) == 1)
					{
						tempEdgeIndex[numTri * 3] = this->GetEdgeOfTet(connOfTri(1), 0);
						tempEdgeIndex[numTri * 3 + 1] = this->GetEdgeOfTet(connOfTri(1), 2);
						tempEdgeIndex[numTri * 3 + 2] = this->GetEdgeOfTet(connOfTri(1), 4);
					}
					else if (connOfTri(2) == 2)
					{
						tempEdgeIndex[numTri * 3] = this->GetEdgeOfTet(connOfTri(1), 1);
						tempEdgeIndex[numTri * 3 + 1] = this->GetEdgeOfTet(connOfTri(1), 2);
						tempEdgeIndex[numTri * 3 + 2] = this->GetEdgeOfTet(connOfTri(1), 5);
					}
					else if (connOfTri(2) == 3)
					{
						tempEdgeIndex[numTri * 3] = this->GetEdgeOfTet(connOfTri(1), 3);
						tempEdgeIndex[numTri * 3 + 1] = this->GetEdgeOfTet(connOfTri(1), 4);
						tempEdgeIndex[numTri * 3 + 2] = this->GetEdgeOfTet(connOfTri(1), 5);
					}
				}
				else
				{

				}
			}
		}
	}

	//sort and unique nodeIndex
	std::sort(tempEdgeIndex, tempEdgeIndex + NbrTri * 3);
	int NbrEdge = (std::unique(tempEdgeIndex, tempEdgeIndex + NbrTri * 3) - tempEdgeIndex);
	edgeIndex = Eigen::VectorXi::Zero(NbrEdge);
	for (int i = 0; i < NbrEdge; i++)
		edgeIndex(i) = tempEdgeIndex[i];

	delete[] tempEdgeIndex;
}

void Mesh_3D::GetTriIndexOfDomain2(Eigen::VectorXi domain, Eigen::VectorXi indexNum,
	Eigen::VectorXi& triIndex, Eigen::VectorXi& triNum)
{
	int NbrTri = 0;

	int NbrDomain = domain.rows();
	for (int i = 0; i < NbrDomain; i++)
	{
		for (int j = 0; j < _mNbrTri; j++)
		{
			if (GetDomainOfTri(j) == domain(i))
				NbrTri++;
		}
	}

	//store triIndex
	triIndex = Eigen::VectorXi::Zero(NbrTri);
	triNum = Eigen::VectorXi::Zero(NbrTri);
	int numTri = 0;
	for (int i = 0; i < NbrDomain; i++)
	{
		for (int j = 0; j < NbrTri; j++)
		{
			if (GetDomainOfTri(j) == domain(i))
			{
				triIndex(numTri) = j;
				triNum(numTri) = i;
				numTri++;
			}
		}
	}
}

void Mesh_3D::GetIndexOfPBC(Eigen::MatrixXi PBC, Eigen::VectorXcd PBCData,
	Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi)
{
	std::vector<Eigen::VectorXi> srcDomains(PBC.rows());
	std::vector<Eigen::VectorXi> dstDomains(PBC.rows());
	Eigen::VectorXd angles = Eigen::VectorXd::Zero(PBC.rows());
	Eigen::MatrixXd waveVecs = Eigen::MatrixXd::Zero(3, PBC.rows());
	for (int i = 0; i < PBC.rows(); i++)
	{
		srcDomains[i] = Eigen::VectorXi(1);
		dstDomains[i] = Eigen::VectorXi(1);
		srcDomains[i](0) = PBC(i, 0);
		dstDomains[i](0) = PBC(i, 1);
	}
	GetIndexOfPBC(srcDomains, dstDomains, PBCData, angles, waveVecs, srcEdgeIndex, dstEdgeIndex, edgePhi);
}

void Mesh_3D::GetIndexOfPBC(const std::vector<Eigen::VectorXi>& srcDomains,
	const std::vector<Eigen::VectorXi>& dstDomains,
	const Eigen::VectorXcd& PBCData, const Eigen::VectorXd& pbcAngles,
	const Eigen::MatrixXd& pbcWaveVecs,
	Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi,
	bool useFaceStyleEdgeTol)
{
	std::vector<int> allSrc, allDst;
	std::vector<std::complex<double>> allPhi;

	for (int pair = 0; pair < static_cast<int>(srcDomains.size()); pair++)
	{
		std::vector<int> srcEdges, dstEdges;
		collectEdgesOnDomains(this, srcDomains[pair], srcEdges);
		collectEdgesOnDomains(this, dstDomains[pair], dstEdges);

		const double theta = (pair < pbcAngles.size()) ? pbcAngles(pair) : 0.0;
		const std::complex<double> blochPhi = (pair < PBCData.size()) ? PBCData(pair) : 1.0;
		Eigen::Vector3d waveVec = Eigen::Vector3d::Zero();
		if (pair < pbcWaveVecs.cols())
			waveVec = pbcWaveVecs.col(pair);

		if (_mNbrCopyOfTri > 0)
		{
			for (int i = 0; i < _mNbrCopyOfTri; i++)
			{
				allSrc.push_back(_mCopyOfTri(i, 0));
				allDst.push_back(_mCopyOfTri(i, 1));
				allPhi.push_back(blochPhi);
			}
			continue;
		}

		std::vector<int> pairedSrc, pairedDst, pairedSign;
		if (waveVec.norm() >= 1e-30)
			pairEdgesByTranslation(this, srcEdges, dstEdges, waveVec, pairedSrc, pairedDst, pairedSign,
				useFaceStyleEdgeTol);
		else
			pairEdgesByGeometry(this, srcEdges, dstEdges, theta, pairedSrc, pairedDst, pairedSign);
		for (size_t i = 0; i < pairedSrc.size(); i++)
		{
			allSrc.push_back(pairedSrc[i]);
			allDst.push_back(pairedDst[i]);
			allPhi.push_back(blochPhi * static_cast<double>(pairedSign[i]));
		}
	}

	removeSelfPairs(allSrc, allDst, allPhi);

	const int n = static_cast<int>(allSrc.size());
	srcEdgeIndex = Eigen::VectorXi(n);
	dstEdgeIndex = Eigen::VectorXi(n);
	edgePhi = Eigen::VectorXcd(n);
	for (int i = 0; i < n; i++)
	{
		srcEdgeIndex(i) = allSrc[i];
		dstEdgeIndex(i) = allDst[i];
		edgePhi(i) = allPhi[i];
	}

	if (n == 0)
		return;

	Eigen::VectorXi tempIndex = Eigen::VectorXi::LinSpaced(n, 0, n - 1);
	QuickSort(dstEdgeIndex, tempIndex, 0, n - 1);
	Unique(dstEdgeIndex, tempIndex);

	Eigen::VectorXcd tempPhi = Eigen::VectorXcd::Zero(dstEdgeIndex.size());
	Eigen::VectorXi tempSrc = Eigen::VectorXi::Zero(dstEdgeIndex.size());
	for (int i = 0; i < dstEdgeIndex.size(); i++)
	{
		tempPhi(i) = edgePhi(tempIndex(i));
		tempSrc(i) = srcEdgeIndex(tempIndex(i));
	}
	edgePhi = tempPhi;
	srcEdgeIndex = tempSrc;
}

void Mesh_3D::GetIndexOfPBCFaces(const std::vector<Eigen::VectorXi>& srcDomains,
	const std::vector<Eigen::VectorXi>& dstDomains,
	const Eigen::VectorXd& pbcAngles,
	const Eigen::MatrixXd& pbcWaveVecs,
	Eigen::VectorXi& srcFaceIndex, Eigen::VectorXi& dstFaceIndex, Eigen::VectorXcd& facePhi)
{
	std::vector<int> allSrc, allDst;
	std::vector<std::complex<double>> allPhi;

	for (int pair = 0; pair < static_cast<int>(srcDomains.size()); pair++)
	{
		// MATLAB findPBCIndex(src,dst,...): all src domains together, all dst together.
		std::vector<int> srcFaces, dstFaces;
		collectFacesOnDomains(this, srcDomains[pair], srcFaces);
		collectFacesOnDomains(this, dstDomains[pair], dstFaces);

		const double theta = (pair < pbcAngles.size()) ? pbcAngles(pair) : 0.0;
		Eigen::Vector3d waveVec = Eigen::Vector3d::Zero();
		if (pair < pbcWaveVecs.cols())
			waveVec = pbcWaveVecs.col(pair);

		std::vector<int> pairedSrc, pairedDst, pairedSign;
		if (waveVec.norm() >= 1e-30)
			pairFacesByTranslation(this, srcFaces, dstFaces, waveVec, pairedSrc, pairedDst, pairedSign);
		else
			pairFacesByGeometry(this, srcFaces, dstFaces, theta, pairedSrc, pairedDst, pairedSign);

		for (size_t i = 0; i < pairedSrc.size(); i++)
		{
			allSrc.push_back(pairedSrc[i]);
			allDst.push_back(pairedDst[i]);
			allPhi.push_back(static_cast<double>(pairedSign[i]));
		}
	}

	removeSelfPairs(allSrc, allDst, allPhi);

	const int n = static_cast<int>(allSrc.size());
	srcFaceIndex = Eigen::VectorXi(n);
	dstFaceIndex = Eigen::VectorXi(n);
	facePhi = Eigen::VectorXcd(n);
	for (int i = 0; i < n; i++)
	{
		srcFaceIndex(i) = allSrc[i];
		dstFaceIndex(i) = allDst[i];
		facePhi(i) = allPhi[i];
	}

	if (n == 0)
		return;

	Eigen::VectorXi tempIndex = Eigen::VectorXi::LinSpaced(n, 0, n - 1);
	QuickSort(dstFaceIndex, tempIndex, 0, n - 1);
	Unique(dstFaceIndex, tempIndex);

	Eigen::VectorXcd tempPhi = Eigen::VectorXcd::Zero(dstFaceIndex.size());
	Eigen::VectorXi tempSrc = Eigen::VectorXi::Zero(dstFaceIndex.size());
	for (int i = 0; i < dstFaceIndex.size(); i++)
	{
		tempPhi(i) = facePhi(tempIndex(i));
		tempSrc(i) = srcFaceIndex(tempIndex(i));
	}
	facePhi = tempPhi;
	srcFaceIndex = tempSrc;
}

// Match MATLAB getMesh.m: unique face rows in column-major order (face slot, then tet).
void Mesh_3D::BuildFaceTopology()
{
	if (_mNbrTet <= 0)
	{
		_mNbrFace = 0;
		return;
	}

	static const int kFaceVert[4][3] = { {0, 1, 2}, {0, 1, 3}, {0, 2, 3}, {1, 2, 3} };
	std::vector<std::array<int, 3>> faces;
	faces.reserve(static_cast<size_t>(_mNbrTet) * 4);
	_mFaceOfTet = Eigen::MatrixXi::Zero(_mNbrTet, 4);

	auto findFace = [&](const std::array<int, 3>& key) -> int {
		for (int i = 0; i < static_cast<int>(faces.size()); i++)
		{
			if (faces[static_cast<size_t>(i)] == key)
				return i;
		}
		return -1;
	};

	for (int f = 0; f < 4; f++)
	{
		for (int t = 0; t < _mNbrTet; t++)
		{
			std::array<int, 3> key = {
				_mTet(t, kFaceVert[f][0]),
				_mTet(t, kFaceVert[f][1]),
				_mTet(t, kFaceVert[f][2])
			};
			int fid = findFace(key);
			if (fid < 0)
			{
				fid = static_cast<int>(faces.size());
				faces.push_back(key);
			}
			_mFaceOfTet(t, f) = fid;
		}
	}

	_mNbrFace = static_cast<int>(faces.size());
	_mFace = Eigen::MatrixXi(_mNbrFace, 3);
	for (int i = 0; i < _mNbrFace; i++)
	{
		_mFace(i, 0) = faces[static_cast<size_t>(i)][0];
		_mFace(i, 1) = faces[static_cast<size_t>(i)][1];
		_mFace(i, 2) = faces[static_cast<size_t>(i)][2];
	}
}
