#ifdef FLOW_ENGINE

#pragma once

#include <pkg/pfv/FlowBoundingSphere.hpp> //include after #define XVIEW
// #include "PeriodicTesselation.h"
// #include "basicVTKwritter.hpp"
// #include "Network.hpp"
//
// #ifdef XVIEW
// #include "Vue3D.h" //FIXME implicit dependencies will look for this class (out of tree) even ifndef XVIEW
// #endif


namespace yade { // Cannot have #include directive inside.

/* TODO:
- remove computePermeability(), mostly a duplicate of the non-periodic version

*/

namespace CGT {

	// 	typedef CGT::FlowBoundingSphere<PeriFlowTesselation> PeriodicFlowBoundingSphere;
	template <class _Tesselation> class PeriodicFlow : public CGT::FlowBoundingSphere<_Tesselation> {
	public:
		typedef _Tesselation         Tesselation;
		typedef Network<Tesselation> _N;
		DECLARE_TESSELATION_TYPES(Network<Tesselation>)
		typedef CGT::FlowBoundingSphere<_Tesselation> BaseFlowSolver;

		//painfull, but we need that for templates inheritance...
		using _N::boundaries;
		using _N::boundingCells;
		using _N::boundsIds;
		using _N::cornerMax;
		using _N::cornerMin;
		using _N::currentTes;
		using _N::debugOut;
		using _N::facetNFictious;
		using _N::facetVertices;
		using _N::Height;
		using _N::idOffset;
		using _N::nOfSpheres;
		using _N::num_particles;
		using _N::Rmoy;
		using _N::sectionArea;
		using _N::sSolidTot;
		using _N::T;
		using _N::vPoral;
		using _N::vPoralPorosity;
		using _N::VSolidTot;
		using _N::vtkInfiniteCells;
		using _N::vtkInfiniteVertices;
		using _N::vTotal;
		using _N::Vtotalissimo;
		using _N::vTotalPorosity;
		using _N::xMax;
		using _N::xMaxId;
		using _N::xMin;
		using _N::xMinId;
		using _N::yMax;
		using _N::yMaxId;
		using _N::yMin;
		using _N::yMinId;
		using _N::zMax;
		using _N::zMaxId;
		using _N::zMin;
		using _N::zMinId;
		using BaseFlowSolver::checkSphereFacetOverlap;
		using BaseFlowSolver::clampKValues;
		using BaseFlowSolver::distanceCorrection;
		using BaseFlowSolver::fluidBulkModulus;
		using BaseFlowSolver::kFactor;
		using BaseFlowSolver::KOptFactor;
		using BaseFlowSolver::maxKdivKmean;
		using BaseFlowSolver::meanKStat;
		using BaseFlowSolver::minKdivKmean;
		using BaseFlowSolver::minPermLength;
		using BaseFlowSolver::noCache;
		using BaseFlowSolver::permeabilityMap;
		using BaseFlowSolver::rAverage;
		using BaseFlowSolver::relax;
		using BaseFlowSolver::resetRHS;
		using BaseFlowSolver::tolerance;
		using BaseFlowSolver::viscosity;

		//same for functions
		using _N::addBoundingPlanes;
		using _N::boundary;
		using _N::defineFictiousCells;
		using _N::surfaceSolidThroatInPore;
		using _N::tesselation;

		void interpolate(Tesselation& Tes, Tesselation& NewTes) override;
		void computeFacetForcesWithCache(bool onlyCache = false) override;
		void computePermeability() override;
		void gaussSeidel(Real dt = 0) override;
		void displayStatistics();
#ifdef EIGENSPARSE_LIB
		//Eigen's sparse matrix for forces computation
		// 		Eigen::SparseMatrix<Real> FIntegral;
		// 		Eigen::SparseMatrix<Real> PshiftsInts;
		// 		Eigen::SparseMatrix<Real> FRHS;
		// 		VectorXr forces;
#endif
	};

	template <class _Tesselation> void PeriodicFlow<_Tesselation>::interpolate(Tesselation& Tes, Tesselation& NewTes)
	{
		CellHandle      oldCell;
		RTriangulation& Tri = Tes.Triangulation();
		for (VCellIterator cellIt = NewTes.cellHandles.begin(); cellIt != NewTes.cellHandles.end(); cellIt++) {
			CellHandle& newCell = *cellIt;
			if (newCell->info().Pcondition || newCell->info().isGhost) continue;
			CVector center(0, 0, 0);
			if (newCell->info().fictious() == 0)
				for (int k = 0; k < 4; k++)
					center = center + 0.25 * (Tes.vertex(newCell->vertex(k)->info().id())->point().point() - CGAL::ORIGIN);
			else {
				Real boundPos = 0;
				int  coord = 0;
				for (int k = 0; k < 4; k++) {
					if (!newCell->vertex(k)->info().isFictious)
						center = center + 0.3333333333 * (Tes.vertex(newCell->vertex(k)->info().id())->point().point() - CGAL::ORIGIN);
					else {
						coord = boundary(newCell->vertex(k)->info().id()).coordinate;
						boundPos = boundary(newCell->vertex(k)->info().id()).p[coord];
					}
				}
				center = CVector(coord == 0 ? boundPos : center[0], coord == 1 ? boundPos : center[1], coord == 2 ? boundPos : center[2]);
			}
			oldCell = Tri.locate(CGT::Sphere(center[0], center[1], center[2]));
			//FIXME: should use getInfo
			newCell->info().p() = oldCell->info().shiftedP();
		}
		//  	Tes.Clear();//Don't reset to avoid segfault when getting pressure in scripts just after interpolation
	}


	template <class _Tesselation> void PeriodicFlow<_Tesselation>::computeFacetForcesWithCache(bool onlyCache)
	{
		RTriangulation&        Tri = T[currentTes].Triangulation();
		CVector                nullVect(0, 0, 0);
		static vector<CVector> oldForces;
		if (oldForces.size() <= Tri.number_of_vertices()) oldForces.resize(Tri.number_of_vertices() + 1);
		//reset forces
		for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) {
			if (noCache) {
				oldForces[v->info().id()] = nullVect;
				v->info().forces = nullVect;
			} else {
				oldForces[v->info().id()] = v->info().forces;
				v->info().forces = nullVect;
			}
		}
		CellHandle   neighbourCell;
		VertexHandle mirrorVertex;
		CVector      tempVect;

		//FIXME : Ema, be carefull with this (noCache), it needs to be turned true after retriangulation
		if (noCache) {
			for (VCellIterator cellIt = T[currentTes].cellHandles.begin(); cellIt != T[currentTes].cellHandles.end(); cellIt++) {
				CellHandle& cell = *cellIt;
				for (int k = 0; k < 4; k++)
					cell->info().unitForceVectors[k] = nullVect;
				for (int j = 0; j < 4; j++)
					if (!Tri.is_infinite(cell->neighbor(j))) {
						// 				#ifdef EIGENSPARSE_LIB
						// 				if (!cell->info().Pcondition) ++nPCells;
						// 				#endif
						neighbourCell = cell->neighbor(j);
						const CVector& Surfk = cell->info().facetSurfaces[j];
						//FIXME : later compute that fluidSurf only once in hydraulicRadius, for now keep full surface not modified in cell->info for comparison with other forces schemes
						//The ratio void surface / facet surface
						Real                        area = sqrt(Surfk.squared_length());
						CVector                     facetNormal = Surfk / area;
						const std::vector<CVector>& crossSections = cell->info().facetSphereCrossSections;
						CVector fluidSurfk = cell->info().facetSurfaces[j] * cell->info().facetFluidSurfacesRatio[j];
						/// handle fictious vertex since we can get the projected surface easily here
						if (cell->vertex(j)->info().isFictious) {
							Real projSurf = abs(Surfk[boundary(cell->vertex(j)->info().id()).coordinate]);
							tempVect = -projSurf * boundary(cell->vertex(j)->info().id()).normal;
							//define the cached value for later use with cache*p
							cell->info().unitForceVectors[j] = cell->info().unitForceVectors[j] + tempVect;
						}
						/// Apply weighted forces f_k=sqRad_k/sumSqRad*f
						CVector facetUnitForce = -fluidSurfk * cell->info().solidSurfaces[j][3];
						for (int y = 0; y < 3; y++) {
							//add to cached value
							cell->info().unitForceVectors[facetVertices[j][y]] = cell->info().unitForceVectors[facetVertices[j][y]]
							        + facetUnitForce * cell->info().solidSurfaces[j][y];
							//uncomment to get total force / comment to get only viscous forces (Bruno)
							if (!cell->vertex(facetVertices[j][y])->info().isFictious) {
								//add to cached value
								cell->info().unitForceVectors[facetVertices[j][y]]
								        = cell->info().unitForceVectors[facetVertices[j][y]]
								        - facetNormal * crossSections[j][y];
							}
						}
					}
			}

			noCache = false; //cache should always be defined after execution of this function
			if (onlyCache) return;
		} // end if(noCache)

		//use cached values
		//First define products that will be used below for all cells:
		Real pDeltas[3];
		for (unsigned int k = 0; k < 3; k++)
			pDeltas[k] = CellInfo::hSize[k] * CellInfo::gradP;
		//Then compute the forces
		for (VCellIterator cellIt = T[currentTes].cellHandles.begin(); cellIt != T[currentTes].cellHandles.end(); cellIt++) {
			const CellHandle& cell = *cellIt;
			for (int yy = 0; yy < 4; yy++) {
				VertexInfo& vhi = cell->vertex(yy)->info();
				Real        unshiftedP = cell->info().p();
				//the pressure translated to a ghost cell adjacent to the non-ghost vertex
				unshiftedP -= pDeltas[0] * vhi.period[0] + pDeltas[1] * vhi.period[1] + pDeltas[2] * vhi.period[2];
				T[currentTes].vertexHandles[vhi.id()]->info().forces
				        = T[currentTes].vertexHandles[vhi.id()]->info().forces + cell->info().unitForceVectors[yy] * unshiftedP;
			}
		}
		if (debugOut) {
			CVector totalForce = nullVect;
			for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); v++) {
				if (!v->info().isFictious /*&& !v->info().isGhost*/) {
					totalForce = totalForce + v->info().forces;
				} else /*if (!v->info().isGhost)*/ {
					if (boundary(v->info().id()).flowCondition == 1) totalForce = totalForce + v->info().forces;
				}
			}
			cout << "totalForce = " << totalForce << endl;
		}
	}

	template <class _Tesselation> void PeriodicFlow<_Tesselation>::computePermeability()
	{
		if (debugOut) cout << "----Computing_Permeability (Periodic)------" << endl;
		RTriangulation& Tri = T[currentTes].Triangulation();
		VSolidTot = 0, Vtotalissimo = 0, vPoral = 0, sSolidTot = 0, vTotalPorosity = 0, vPoralPorosity = 0;
		CellHandle neighbourCell;

		Real        k = 0, distance = 0, radius = 0;
		int         surfneg = 0;
		int         NEG = 0, POS = 0, pass = 0;
		Real        meanK = 0, STDEV = 0, meanRadius = 0, meanDistance = 0;
		Real        infiniteK = 1e3;
		Real        volume_sub_pore = 0.f;
		VectorCell& cellHandles = T[currentTes].cellHandles;
		for (VCellIterator cellIt = T[currentTes].cellHandles.begin(); cellIt != T[currentTes].cellHandles.end(); cellIt++) {
			CellHandle& cell = *cellIt;
			Point&      p1 = cell->info();
			if (cell->info().blocked) { this->setBlocked(cell); }
			if (cell->info().isGhost) {
				cerr << "skipping a ghost" << endl;
				continue;
			}
			for (int j = 0; j < 4; j++) {
				neighbourCell = cell->neighbor(j);
				Point& p2 = neighbourCell->info();
				if (!Tri.is_infinite(neighbourCell) /*&& (neighbour_cell->info().isvisited==ref || computeAllCells)*/) {
					//compute and store the area of sphere-facet intersections for later use
					VertexHandle W[3];
					for (int kk = 0; kk < 3; kk++) {
						W[kk] = cell->vertex(facetVertices[j][kk]);
					}
					Sphere& v0 = W[0]->point();
					Sphere& v1 = W[1]->point();
					Sphere& v2 = W[2]->point();
#ifdef USE_FAST_MATH
					//FIXME : code not compiling,, do the same as in "else"
					assert((W[permut3[jj][1]]->point() - W[permut3[jj][0]]->point())
					                       * (W[permut3[jj][2]]->point() - W[permut3[jj][0]]->point())
					               >= 0
					       && (W[permut3[jj][1]]->point() - W[permut3[jj][0]]->point())
					                       * (W[permut3[jj][2]]->point() - W[permut3[jj][0]]->point())
					               <= 1);
					for (int jj = 0; jj < 3; jj++)
						cell->info().facetSphereCrossSections[j][jj] = 0.5 * W[jj]->point().weight()
						        * Wm3::FastInvCos1((W[permut3[jj][1]]->point() - W[permut3[jj][0]]->point())
						                           * (W[permut3[jj][2]]->point() - W[permut3[jj][0]]->point()));
#else
					cell->info().facetSphereCrossSections[j] = CVector(
					        W[0]->info().isFictious ? 0
					                                : 0.5 * v0.weight()
					                        * acos((v1.point() - v0.point()) * (v2.point() - v0.point())
					                               / sqrt((v1.point() - v0.point()).squared_length()
					                                      * (v2.point() - v0.point()).squared_length())),
					        W[1]->info().isFictious ? 0
					                                : 0.5 * v1.weight()
					                        * acos((v0.point() - v1.point()) * (v2.point() - v1.point())
					                               / sqrt((v1.point() - v0.point()).squared_length()
					                                      * (v2.point() - v1.point()).squared_length())),
					        W[2]->info().isFictious ? 0
					                                : 0.5 * v2.weight()
					                        * acos((v0.point() - v2.point()) * (v1.point() - v2.point())
					                               / sqrt((v1.point() - v2.point()).squared_length()
					                                      * (v2.point() - v0.point()).squared_length())));
#endif
					//FIXME: it should be possible to skip completely blocked cells, currently the problem is it segfault for undefined areas
					//if (cell->info().blocked) continue;//We don't need permeability for blocked cells, it will be set to zero anyway

					pass += 1;
					CVector l = p1 - p2;
					distance = sqrt(l.squared_length());
					if (!rAverage) radius = 2 * this->computeHydraulicRadius(cell, j);
					else
						radius = (this->computeEffectiveRadius(cell, j) + this->computeEquivalentRadius(cell, j)) * 0.5;
					if (radius < 0) NEG++;
					else
						POS++;
					if (radius == 0) { cout << "INS-INS PROBLEM!!!!!!!" << endl; }
					Real fluidArea = 0;
					int  test = 0;
					if (distance != 0) {
						if (minPermLength > 0 && distanceCorrection) distance = math::max(minPermLength * radius, distance);
						const CVector& Surfk = cell->info().facetSurfaces[j];
						Real           area = sqrt(Surfk.squared_length());
						const CVector& crossSections = cell->info().facetSphereCrossSections[j];
						Real           S0 = 0;
						S0 = checkSphereFacetOverlap(v0, v1, v2);
						if (S0 == 0) S0 = checkSphereFacetOverlap(v1, v2, v0);
						if (S0 == 0) S0 = checkSphereFacetOverlap(v2, v0, v1);
						//take absolute value, since in rare cases the surface can be negative (overlaping spheres)
						fluidArea = abs(area - crossSections[0] - crossSections[1] - crossSections[2] + S0);
						cell->info().facetFluidSurfacesRatio[j] = fluidArea / area;
						// kFactor<0 means we replace Poiseuille by Darcy localy, yielding a particle size-independent bulk conductivity
						if (kFactor > 0) cell->info().kNorm()[j] = kFactor * (fluidArea * pow(radius, 2)) / (8 * viscosity * distance);
						else
							cell->info().kNorm()[j] = -kFactor * area / distance;
						meanDistance += distance;
						meanRadius += radius;
						meanK += (cell->info().kNorm())[j];

						if (k < 0 && debugOut) {
							surfneg += 1;
							cout << "__ k<0 __" << k << " "
							     << " fluidArea " << fluidArea << " area " << area << " " << crossSections[0] << " "
							     << crossSections[1] << " " << crossSections[2] << " " << W[0]->info().id() << " "
							     << W[1]->info().id() << " " << W[2]->info().id() << " " << p1 << " " << p2 << " test " << test
							     << endl;
						}
					} else {
						cout << "infinite K2! surfaces will be missing (FIXME)" << endl;
						k = infiniteK;
					}
					//Will be corrected in the next loop
					if (!neighbourCell->info().isGhost)
						(neighbourCell->info().kNorm())[Tri.mirror_index(cell, j)] = (cell->info().kNorm())[j];
					//The following block is correct but not very usefull, since all values are clamped below with MIN and MAX, skip for now
					// 					else {//find the real neighbor connected to our cell through periodicity
					// 						CellHandle true_neighbour_cell = cellHandles[neighbour_cell->info().baseIndex];
					// 						for (int ii=0;ii<4;ii++)
					// 							if (true_neighbour_cell->neighbor(ii)->info().index == cell->info().index){
					// 								(true_neighbour_cell->info().kNorm())[ii]=(cell->info().kNorm())[j]; break;
					// 							}
					// 					}
					if (permeabilityMap) {
						CellHandle c = cell;
						cell->info().s = cell->info().s + k * distance / fluidArea * this->volumePoreVoronoiFraction(c, j);
						volume_sub_pore += this->volumePoreVoronoiFraction(c, j);
					}
				}
			}
			// 			cell->info().isvisited = !ref;
			if (permeabilityMap) {
				cell->info().s = cell->info().s / volume_sub_pore;
				volume_sub_pore = 0.f;
			}
			// 		}
		}


		if (debugOut) cout << "surfneg est " << surfneg << endl;
		meanK /= pass;
		meanRadius /= pass;
		meanDistance /= pass;
		Real globalK = kFactor * meanDistance * vPoral
		        / (sSolidTot * 8. * viscosity); //An approximate value of macroscopic permeability, for clamping local values below
		if (debugOut) {
			cout << "PassCompK = " << pass << endl;
			cout << "meanK = " << meanK << endl;
			cout << "globalK = " << globalK << endl;
			cout << "maxKdivKmean*globalK = " << maxKdivKmean * globalK << endl;
			cout << "minKdivKmean*globalK = " << minKdivKmean * globalK << endl;
			cout << "meanTubesRadius = " << meanRadius << endl;
			cout << "meanDistance = " << meanDistance << endl;
		}
		pass = 0;
		if (clampKValues)
			for (VCellIterator cellIt = T[currentTes].cellHandles.begin(); cellIt != T[currentTes].cellHandles.end(); cellIt++) {
				CellHandle& cell = *cellIt;
				for (int j = 0; j < 4; j++) {
					neighbourCell = cell->neighbor(j);
					if (!Tri.is_infinite(neighbourCell) /*&& neighbour_cell->info().isvisited==ref*/) {
						pass++;
						(cell->info().kNorm())[j]
						        = math::max(minKdivKmean * globalK, math::min((cell->info().kNorm())[j], maxKdivKmean * globalK));
						(neighbourCell->info().kNorm())[Tri.mirror_index(cell, j)] = (cell->info().kNorm())[j];
						if (!neighbourCell->info().isGhost)
							(neighbourCell->info().kNorm())[Tri.mirror_index(cell, j)] = (cell->info().kNorm())[j];
						else { //find the real neighbor connected to our cell through periodicity, as we want exactly the same permeability without rounding errors
							CellHandle& true_neighbourCell = cellHandles[neighbourCell->info().baseIndex];
							for (int ii = 0; ii < 4; ii++)
								if (true_neighbourCell->neighbor(ii)->info().index == cell->info().index) {
									(true_neighbourCell->info().kNorm())[ii] = (cell->info().kNorm())[j];
									break;
								}
						}
					}
				}
			}
		if (debugOut) cout << "PassKcorrect = " << pass << endl;

		if (debugOut) cout << "POS = " << POS << " NEG = " << NEG << " pass = " << pass << endl;

		// A loop to compute the standard deviation of the local K distribution, and use it to include/exclude K values higher then (meanK +/- K_opt_factor*STDEV)
		if (meanKStat) {
			std::ofstream k_opt_file("k_stdev.txt", std::ios::out);
			pass = 0;
			FiniteCellsIterator cellEnd = Tri.finite_cells_end();
			for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) {
				for (int j = 0; j < 4; j++) {
					neighbourCell = cell->neighbor(j);
					if (!Tri.is_infinite(neighbourCell) /*&& neighbour_cell->info().isvisited==ref*/) {
						pass++;
						STDEV += pow(((cell->info().kNorm())[j] - meanK), 2);
					}
				}
			}
			STDEV = sqrt(STDEV / pass);
			if (debugOut) cout << "PassSTDEV = " << pass << endl;
			cout << "STATISTIC K" << endl;
			Real k_min = 0, k_max = meanK + KOptFactor * STDEV;
			cout << "Kmoy = " << meanK << " Standard Deviation = " << STDEV << endl;
			cout << "kmin = " << k_min << " kmax = " << k_max << endl;
			pass = 0;
			for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) {
				for (int j = 0; j < 4; j++) {
					neighbourCell = cell->neighbor(j);
					if (!Tri.is_infinite(neighbourCell) /*&& neighbour_cell->info().isvisited==ref*/) {
						pass += 1;
						if ((cell->info().kNorm())[j] > k_max) {
							(cell->info().kNorm())[j] = k_max;
							(neighbourCell->info().kNorm())[Tri.mirror_index(cell, j)] = (cell->info().kNorm())[j];
						}
						k_opt_file << KOptFactor << " " << (cell->info().kNorm())[j] << endl;
					}
				}
			}
			if (debugOut) cout << "PassKopt = " << pass << endl;
		}
		if (debugOut) {
			FiniteVerticesIterator verticesEnd = Tri.finite_vertices_end();
			Real                   Vgrains = 0;
			int                    grains = 0;

			for (FiniteVerticesIterator vIt = Tri.finite_vertices_begin(); vIt != verticesEnd; vIt++) {
				if (!vIt->info().isFictious && !vIt->info().isGhost) {
					grains += 1;
					Vgrains += 1.33333333 * M_PI * pow(vIt->point().weight(), 1.5);
				}
			}
			cout << grains << "grains - "
			     << "vTotal = " << vTotal << " Vgrains = " << Vgrains << " vPoral1 = " << (vTotal - Vgrains) << endl;
			cout << "Vtotalissimo = " << Vtotalissimo / 2 << " VSolidTot = " << VSolidTot / 2 << " vPoral2 = " << vPoral / 2
			     << " sSolidTot = " << sSolidTot << endl
			     << endl;

			if (!rAverage) cout << "------Hydraulic Radius is used for permeability computation------" << endl << endl;
			else
				cout << "------Average Radius is used for permeability computation------" << endl << endl;
			cout << "-----computed_Permeability Periodic-----" << endl;
		}
	}


	template <class _Tesselation> void PeriodicFlow<_Tesselation>::gaussSeidel(Real dt)
	{
		RTriangulation& Tri = T[currentTes].Triangulation();
		int             j = 0;
		Real            m, n, dp_max, p_max, sum_p, dp, sum_dp;
		Real            compFlowFactor = 0;
		vector<Real>    previousP;
		previousP.resize(Tri.number_of_finite_cells());
		const int num_threads = 1;
		bool      compressible = fluidBulkModulus > 0;

		vector<Real> t_sum_p, t_dp_max, t_sum_dp, t_p_max;
		t_sum_dp.resize(num_threads);
		t_dp_max.resize(num_threads);
		t_p_max.resize(num_threads);
		t_sum_p.resize(num_threads);
		FiniteCellsIterator cellEnd = Tri.finite_cells_end();
		do {
			int cell2 = 0;
			dp_max = 0;
			p_max = 0;
			sum_p = 0;
			sum_dp = 0;
			int bb = -1;
			for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) {
				bb++;
				if (!cell->info().Pcondition && !cell->info().isGhost) {
					cell2++;
					if (compressible && j == 0) previousP[bb] = cell->info().shiftedP();
					m = 0, n = 0;
					for (int j2 = 0; j2 < 4; j2++) {
						if (!Tri.is_infinite(cell->neighbor(j2))) {
							if (compressible) {
								compFlowFactor = fluidBulkModulus * dt * cell->info().invVoidVolume();
								m += compFlowFactor * (cell->info().kNorm())[j2] * cell->neighbor(j2)->info().shiftedP();
								if (j == 0) n += compFlowFactor * (cell->info().kNorm())[j2];
							} else {
								m += (cell->info().kNorm())[j2] * cell->neighbor(j2)->info().shiftedP();
								if (math::isinf(m) && j < 10)
									cout << "(cell->info().kNorm())[j2] = " << (cell->info().kNorm())[j2]
									     << " cell->neighbor(j2)->info().shiftedP() = "
									     << cell->neighbor(j2)->info().shiftedP() << endl;
								if (j == 0) n += (cell->info().kNorm())[j2];
							}
						}
					}
					dp = cell->info().p();
					if (n != 0 || j != 0) {
						if (j == 0) {
							if (compressible) cell->info().invSumK = 1 / (1 + n);
							else
								cell->info().invSumK = 1 / n;
						}
						if (compressible) {
							cell->info().setP(
							        (((previousP[bb]
							           - ((fluidBulkModulus * dt * cell->info().invVoidVolume()) * (cell->info().dv())))
							          + m) * cell->info().invSumK
							         - cell->info().shiftedP())
							                * relax
							        + cell->info().shiftedP());
						} else {
							cell->info().setP(
							        (-(cell->info().dv() - m) * cell->info().invSumK - cell->info().p()) * relax
							        + cell->info().shiftedP());
						}
					}
					dp -= cell->info().p();
					dp_max = math::max(dp_max, math::abs(dp));
					p_max = math::max(p_max, math::abs(cell->info().shiftedP()));
					sum_p += cell->info().shiftedP();
					sum_dp += math::abs(dp);
				}
			}
			j++;

			if (j >= 40000) cerr << "\r GS not converged after 40k iterations, break";

		} while ((dp_max / p_max) > tolerance && j < 40000 /*&& ( dp_max > tolerance )*/ /* &&*/ /*( j<50 )*/);

		int  cel = 0;
		Real Pav = 0;
		for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) {
			cel++;
			Pav += cell->info().shiftedP();
		}
		Pav /= cel;
	}

	template <class _Tesselation> void PeriodicFlow<_Tesselation>::displayStatistics()
	{
		RTriangulation&     Tri = T[currentTes].Triangulation();
		int                 Zero = 0, Inside = 0, Fictious = 0, ghostC = 0, realC = 0, ghostV = 0, realV = 0;
		FiniteCellsIterator cellEnd = Tri.finite_cells_end();
		for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) {
			int zeros = 0;
			for (int j = 0; j != 4; j++)
				if ((cell->info().kNorm())[j] == 0) zeros += 1;
			if (zeros == 4) Zero += 1;
			if (!cell->info().fictious()) Inside += 1;
			else
				Fictious += 1;
			if (cell->info().isGhost) ghostC += 1;
			else
				realC += 1;
		}
		int fict = 0, real = 0;
		for (FiniteVerticesIterator v = Tri.finite_vertices_begin(); v != Tri.finite_vertices_end(); ++v) {
			if (v->info().isFictious) fict += 1;
			else
				real += 1;
		}
		long Vertices = Tri.number_of_vertices();
		long Cells = Tri.number_of_finite_cells();
		long Facets = Tri.number_of_finite_facets();
		if (debugOut) {
			cout << "zeros = " << Zero << endl;
			cout << "There are " << Vertices << " vertices, dont " << fict << " fictious et " << real << " reeeeeel" << std::endl;
			cout << "There are " << ghostV + realV << " vertices, dont " << ghostV << " ghost et " << realV << " reeeeeel" << std::endl;
			cout << "There are " << ghostC + realC << " cells, dont " << ghostC << " ghost et " << realC << " reeeeeel" << std::endl;
			cout << "There are " << Cells << " cells " << std::endl;
			cout << "There are " << Facets << " facets " << std::endl;
			cout << "There are " << Inside << " cells INSIDE." << endl;
			cout << "There are " << Fictious << " cells FICTIOUS." << endl;
		}
		vtkInfiniteVertices = fict;
		vtkInfiniteCells = Fictious;
		num_particles = real;
	}

} //END NAMESPACE CGT

} // namespace yade

#ifdef LINSOLV
#include "PeriodicFlowLinSolv.hpp"
#endif


#endif //FLOW_ENGINE
