#pragma once

#include "../Eigen/Dense"
#include "../Eigen/Sparse"
#include "../mesh/Mesh_Base.h"
#include "../phy/Phy_Base.h"
#include "../common/util.h"

#include <complex>
#include <vector>

struct PbcEdgePairs
{
	std::vector<int> src;
	std::vector<int> dst;
	std::vector<std::complex<double>> sign;
};

PbcEdgePairs collectPbcEdgePairs(
	Mesh_3D* mesh,
	const std::vector<Eigen::VectorXi>& srcDomains,
	const std::vector<Eigen::VectorXi>& dstDomains,
	const Eigen::VectorXd& pbcAngles,
	const Eigen::MatrixXd& pbcWaveVecs,
	bool useFaceStyleEdgeTol = false);

void mergeDoublePbcPairs(
	const PbcEdgePairs& pairs1,
	std::complex<double> blochPhi1,
	const PbcEdgePairs& pairs2,
	std::complex<double> blochPhi2,
	PbcEdgePairs& merged,
	std::vector<std::complex<double>>& mergedPhi);

void sortUniquePbcPairs(
	Eigen::VectorXi& srcEdgeIndex,
	Eigen::VectorXi& dstEdgeIndex,
	Eigen::VectorXcd& edgePhi);

void fillPbcGroupFromPhy(
	const Phy_WaveOpticsModel* phy,
	int group,
	std::vector<Eigen::VectorXi>& srcDomains,
	std::vector<Eigen::VectorXi>& dstDomains,
	Eigen::VectorXd& pbcAngles,
	Eigen::MatrixXd& pbcWaveVecs);

bool collectMergedPbcConstraints(
	Mesh_3D* mesh,
	const Phy_WaveOpticsModel* phy,
	int elementOrder,
	Eigen::VectorXi& srcDofIndex,
	Eigen::VectorXi& dstDofIndex,
	Eigen::VectorXcd& dofPhi);

bool assemblePbcProjectionMatrix(
	Mesh_3D* mesh,
	const Phy_WaveOpticsModel* phy,
	int elementOrder,
	int dof,
	bool isReal,
	Eigen::SparseMatrix<double, Eigen::RowMajor>& P_real,
	Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>& P_complex);

void buildPeriodicProjectionReal(
	int dof,
	const Eigen::VectorXi& dstIndex,
	const Eigen::VectorXi& srcIndex,
	const Eigen::VectorXcd& indexPhi,
	Eigen::SparseMatrix<double, Eigen::RowMajor>& P);

void buildPeriodicProjectionComplex(
	int dof,
	const Eigen::VectorXi& dstIndex,
	const Eigen::VectorXi& srcIndex,
	const Eigen::VectorXcd& indexPhi,
	Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>& P);
