/*************************************************************************
*  Copyright (C) 2010 by Bruno Chareyre <bruno.chareyre@hmg.inpg.fr>     *
*                                                                        *
*  This program is free software; it is licensed under the terms of the  *
*  GNU General Public License v2 or later. See file LICENSE for details. *
*************************************************************************/

#ifdef FLOW_ENGINE


#if CGAL_VERSION_NR < CGAL_VERSION_NUMBER(4, 11, 0)
#include "CGAL/constructions/constructions_on_weighted_points_cartesian_3.h"
#endif
#include "vector"
#include <CGAL/Width_3.h>
#include <assert.h>
#include <fstream>
#include <iostream>
#include <new>
#include <sys/stat.h>
#include <sys/types.h>
#include <utility>

#ifdef YADE_OPENMP
#include <omp.h>
#endif

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

namespace CGT {

#ifdef PARDISO
#ifdef AIX
#define F77_FUNC(func) func
#else
#define F77_FUNC(func) func##_
#endif
	/* PARDISO prototype. */
	// Note about using high precisision Real, for start move these prototypes to file lib/compatibility/PardisoCompatibility.hpp,
	// and do similar wrapper/converter like lib/compatibility/LapackCompatibility.cpp is done
	// then try to replace it with native Eigen high-precision Real roooutines.
	extern "C" int F77_FUNC(pardisoinit)(void*, int*, int*, int*, double*, int*);

	extern "C" int F77_FUNC(pardiso)(void*, int*, int*, int*, int*, int*, double*, int*, int*, int*, int*, int*, int*, double*, double*, int*, double*);
#endif
	template <class _Tesselation> PeriodicFlowLinSolv<_Tesselation>::~PeriodicFlowLinSolv() { }
	template <class _Tesselation>
	PeriodicFlowLinSolv<_Tesselation>::PeriodicFlowLinSolv()
	        : BaseFlowSolver()
	{
	}

	template <class _Tesselation> int PeriodicFlowLinSolv<_Tesselation>::setLinearSystem(Real dt)
	{
		using math::max;
		using math::min;

		//WARNING : boundary conditions (Pcondition, p values) must have been set for a correct definition of
		RTriangulation& Tri = T[currentTes].Triangulation();
		vector<int>     clen;
		vector<int>     is;
		vector<int>     js;
		vector<Real>    vs;
		if (!areCellsOrdered) {
			T_nnz = 0;
			ncols = 0;
			T_cells.clear();
			T_index = 0;
			unsigned int maxindex = 0;
			//FIXME: this is way too large since many cells will be ghosts
			T_cells.resize(Tri.number_of_finite_cells() + 1);
			///Ordered cells
			orderedCells.clear();
			const FiniteCellsIterator cellEnd = Tri.finite_cells_end();
			for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) {
				if (cell->info().Pcondition || cell->info().isGhost) continue;
				orderedCells.push_back(cell);
				// 			T_cells[++ncols]=cell;
				// 			indices[cell->info().index]=ncols;
				++ncols;
				T_cells[cell->info().index] = cell;
				maxindex = max(maxindex, cell->info().index);
			}
			// 		spatial_sort(orderedCells.begin(),orderedCells.end(), CellTraits_for_spatial_sort());//FIXME: ordering will not work with the new "indices", which needs reordering to
			T_cells.resize(ncols + 1);
			isLinearSystemSet = false;
			areCellsOrdered = true;
		}
		if (!isLinearSystemSet) {
			int n = 3 * (ncols + 1); //number of non-zero in triangular matrix
			is.resize(n);
			js.resize(n);
			vs.resize(n);
			T_x.resize(ncols);
			T_b.resize(ncols);
			T_bv.resize(ncols);
			bodv.resize(ncols);
			xodv.resize(ncols);
			T_cells.resize(ncols + 1);
			T_nnz = 0;
		}
		for (int kk = 0; kk < ncols; kk++)
			T_b[kk] = 0;
		///Ordered cells
		int        nIndex = 0;
		CellHandle neighbourCell;
		for (int i2 = 0; i2 < ncols; i2++) {
			FiniteCellsIterator& cell = orderedCells[i2];
			///Non-ordered cells
			{
				const int&      index = cell->info().index;
				const CellInfo& cInfo = cell->info();
				if (!isLinearSystemSet) {
					//Add diagonal term
					is[T_nnz] = index;
					js[T_nnz] = index;
					vs[T_nnz] = (cInfo.kNorm())[0] + (cInfo.kNorm())[1] + (cInfo.kNorm())[2] + (cInfo.kNorm())[3];
					if (vs[T_nnz] < 0) cerr << "!!!! WTF !!!" << endl;
					if (fluidBulkModulus > 0) vs[T_nnz] += (1.f / (dt * fluidBulkModulus * cInfo.invVoidVolume()));
					T_nnz++;
				}
				for (int j = 0; j < 4; j++) {
					neighbourCell = cell->neighbor(j);
					if (Tri.is_infinite(neighbourCell)) continue;
					CellInfo& nInfo = neighbourCell->info();
					nIndex = nInfo.index;
					if (nIndex == index) {
						cerr << "ERROR: nIndex==index, the cell is neighbour to itself" << index << endl;
						errorCode = 3;
					}

					if (nInfo.Pcondition) T_b[index - 1] += cInfo.kNorm()[j] * nInfo.shiftedP();
					else {
						if (!isLinearSystemSet && index > nIndex) {
							is[T_nnz] = index;
							js[T_nnz] = nIndex;
							vs[T_nnz] = -(cInfo.kNorm())[j];
							if (vs[T_nnz] > 0) cerr << "!!!! WTF2 !!!" << endl;
							T_nnz++;
						}
						if (nInfo.isGhost) T_b[index - 1] += cInfo.kNorm()[j] * nInfo.pShift();
					}
				}
			}
		}
		if (!isLinearSystemSet) {
			if (useSolver > 0) {
#ifdef LINSOLV
				tripletList.clear();
				tripletList.resize(T_nnz);
				for (int k = 0; k < T_nnz; k++) {
					tripletList[k] = ETriplet(is[k] - 1, js[k] - 1, vs[k]);
				}
				A.resize(ncols, ncols);
				A.setFromTriplets(tripletList.begin(), tripletList.end());
#else
				cerr << "yade compiled without CHOLMOD, FlowEngine.useSolver=" << useSolver << ">0 not supported" << endl;
#endif
			}
			isLinearSystemSet = true;
		}
		return ncols;
	}


	/// For Gauss Seidel, we need the full matrix
	template <class _Tesselation> int PeriodicFlowLinSolv<_Tesselation>::setLinearSystemFullGS(Real dt)
	{
		//WARNING : boundary conditions (Pcondition, p values) must have been set for a correct definition
		RTriangulation& Tri = T[currentTes].Triangulation();
		int             n_cells = Tri.number_of_finite_cells();

		if (!isFullLinearSystemGSSet) {
			T_cells.clear();
			T_index = 0; //FIXME : no need to clear if we don't re-triangulate
			T_nnz = 0;
			ncols = 0;
			const FiniteCellsIterator cellEnd = Tri.finite_cells_end();
			orderedCells.clear();
			T_cells.resize(n_cells + 1);
			for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) {
				if (cell->info().Pcondition || cell->info().isGhost) continue;
				++ncols;
				T_cells[cell->info().index] = cell;
			}
			// 		spatial_sort(orderedCells.begin(),orderedCells.end(), CellTraits_for_spatial_sort());
			gsP.resize(ncols + 1);
			gsB.resize(ncols + 1);
			T_b.resize(ncols + 1);
			gsdV.resize(ncols + 1);
			fullAcolumns.resize(ncols + 1);
			fullAvalues.resize(ncols + 1);
			T_cells.resize(ncols + 1);
			for (int k = 0; k <= ncols; k++) {
				fullAcolumns[k].resize(4);
				fullAvalues[k].resize(5);
				gsdV[k] = 0;
			}
			gsP[0] = 0;
			areCellsOrdered = true;
		}
		for (int k = 0; k <= ncols; k++)
			gsB[k] = 0;
		if (!isFullLinearSystemGSSet) {
			///Ordered cells
			for (int i2 = 1; i2 <= ncols; i2++) {
				CellHandle& cell = T_cells[i2];
				///Non-ordered cells
				if (!cell->info().Pcondition && !cell->info().isGhost) {
					//Add diagonal term
					fullAvalues[cell->info().index][4] = 1.f
					        / ((cell->info().kNorm())[0] + (cell->info().kNorm())[1] + (cell->info().kNorm())[2] + (cell->info().kNorm())[3]
					           + (fluidBulkModulus > 0 ? 1.f / (dt * fluidBulkModulus * cell->info().invVoidVolume()) : 0));
					//DUMP
					T_nnz++;
					for (int j = 0; j < 4; j++) {
						CellHandle      neighbourCell = cell->neighbor(j);
						const CellInfo& nInfo = neighbourCell->info();
						CellInfo&       cInfo = cell->info();
						if (Tri.is_infinite(neighbourCell)) {
							fullAvalues[cInfo.index][j] = 0;
							//We point to the pressure of the adjacent cell. If the cell is ghost, then it has the index of the real one, and then the pointer is correct
							fullAcolumns[cInfo.index][j] = &gsP[0];
							continue;
						}
						if (!nInfo.Pcondition) {
							++T_nnz;
							fullAvalues[cInfo.index][j] = (cInfo.kNorm())[j];
							fullAcolumns[cInfo.index][j] = &gsP[nInfo.index];
							//DUMP
							//if the adjacent cell is ghost, we account for the pressure shift in the RHS
							if (nInfo.isGhost) { gsB[cInfo.index] += cInfo.kNorm()[j] * nInfo.pShift(); }
						} else {
							fullAvalues[cInfo.index][j] = 0;
							fullAcolumns[cInfo.index][j] = &gsP[0];
							gsB[cInfo.index] += cInfo.kNorm()[j] * nInfo.shiftedP();
						}
					}
				}
			}
		} else
			for (int i2 = 1; i2 <= ncols; i2++) {
				CellHandle& cell = T_cells[i2];
				///Non-ordered cells
				if (!cell->info().Pcondition && !cell->info().isGhost) {
					for (int j = 0; j < 4; j++) {
						CellHandle      neighbourCell = cell->neighbor(j);
						const CellInfo& nInfo = neighbourCell->info();
						CellInfo&       cInfo = cell->info();
						if (!nInfo.Pcondition) {
							if (nInfo.isGhost) gsB[cInfo.index] += cInfo.kNorm()[j] * nInfo.pShift();
						} else
							gsB[cInfo.index] += cInfo.kNorm()[j] * nInfo.shiftedP();
					}
				}
			}
		isFullLinearSystemGSSet = true;
		return ncols;
	}

} //namespace CGT
}; // namespace yade

#endif //FLOW_ENGINE
