#ifndef RFM_H
#define RFM_H

#define EIGEN_NO_DEBUG
#define EIGEN_USE_MKL_ALL
#define MKL_LP64

#include <cstdint>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <vector>
#include <array>
#include <list>
#include <Eigen/Core>
#include <Eigen/Sparse>
#include <mkl.h>
#include <Eigen/PardisoSupport>
using namespace std;
using namespace Eigen;

struct RFM {
	uint32_t N;
	uint32_t bs;
	uint32_t nedges0;

	vector<uint32_t> &sel;
	vector<double> &ths;
	list<array<double,2>> vi;
	list<uint32_t> sbl; //sorted brokenlist

	RFM(uint32_t N, uint32_t bs, 
		vector<uint32_t> &sel, 
		vector<double> &ths) : N(N), bs(bs), sel(sel), ths(ths) 
	{	
		nedges0 = ths.size();
		if(sel.size()/2 != nedges0) {
			cout << "Error: edge number" << endl;
		}
	}

	void run(uint32_t niter) {
		Map<ArrayXd> thresholds(ths.data(),nedges0);
		// Diagonal matrix of conductivites
		SparseMatrix<int64_t> cond(nedges0,nedges0);
		cond.reserve(VectorXi::Constant(nedges0,1)); //1 element per column
		for(auto e=0; e<nedges0; ++e) {
		    cond.insert(e,e) = 1;
		}
		cond.makeCompressed();     
		// Transposed signed incidence matrix, column major (blame eigen)
		SparseMatrix<int64_t> incT(N,nedges0);
		incT.reserve(VectorXi::Constant(nedges0,2));
		for(auto e=0; e<nedges0; ++e)
		{
		  incT.insert(sel[2*e],e) = -1;
		  incT.insert(sel[2*e+1],e) = 1;
		}
		incT.makeCompressed();   
		
		
		auto Nt = N-bs;
		auto Nb = bs;
		auto n = Nt-Nb;
		VectorXd u(N);
		fill(u.begin(),u.end(),0);
		fill(u.begin()+Nt,u.end(),1);
		vi.emplace_back(array<double,2>{0,0});
		for(uint32_t iter=0; iter<niter; ++iter) {
		    auto ci = cond * (incT.transpose());
		    SparseMatrix<double> lap(N,N);
		    lap = (incT * ci).pruned().cast<double>();
		    // SparseMatrix<double> dirichlet = lap.block(Nb,Nb, n,n).triangularView<Lower>(); //for SimplicialLLT 
		    SparseMatrix<double> dirichlet = lap.block(Nb,Nb, n,n).triangularView<Upper>(); //for PardisoLLT
		    //implicit cast to SparseMatrix<double> from Block
		    dirichlet.makeCompressed();
		    auto mat = lap.block(Nb,Nt, n,N-Nt);
		    VectorXd rhs(n);
		    rhs = -mat * (u.tail(N-Nt));
		    // SimplicialLLT<SparseMatrix<double>> chol(dirichlet);
		    PardisoLLT<SparseMatrix<double>, Upper> chol(dirichlet);
		    // chol.pardisoParameterArray()[1] = 0; //serial minimum degree algorithm  
		    chol.pardisoParameterArray()[1] = 3; //omp nested dissection algorithm
		    // chol.pardisoParameterArray()[59] = 1; //out-of-core pardiso
		    u(seqN(Nb,n)) = chol.solve(rhs);
		    auto m1 = lap.block(Nt,Nb, N-Nt,n);
		    auto m2 = lap.block(Nt,Nt, N-Nt,N-Nt);
		    ArrayXd top_i = m1*u(seqN(Nb,n)) + m2*u(seqN(Nt,N-Nt));

		    double nonnorm_i = top_i.sum();
		    if(abs(nonnorm_i)<1e-8) {
		        vi.emplace_back(array<double,2>{0,0});
		        cout << "Conductivity was lost at iter " << iter << endl;
		        break;
		    }
		    else {
		        ArrayXd loc_i = ci.cast<double>()*u;
		        auto ratios = loc_i.abs()/thresholds;
		        auto d = max_element(ratios.begin(),ratios.end());
		        double ivscale = *d;
		        double glo_i = nonnorm_i/ivscale;
		        double glo_v = u.tail(N-Nt).mean()/ivscale;                
		        vi.push_back(array<double,2>{glo_v,glo_i});
		        auto eid = distance(ratios.begin(),d);
		        cond.coeffRef(eid,eid) = 0;
		        sbl.push_back(eid);
		    }    
		}

	}







	

	void export_iv(const char* filename) {
	    ofstream f;
	    f.open(filename);
	    f.precision(17);
	    for(auto& l: vi)  {
	        f << l[0] << " " << l[1] << endl;
	    }
	    f.close();
	}

	void export_sbl(const char* filename) {
	    ofstream f;
	    f.open(filename);
	    for(auto& l: sbl)  {
	        f << l << endl;
	    }
	    f.close();
	}

	void export_ths(const char* filename) {
	    ofstream f;
	    f.open(filename);
	    for(auto& l: ths)  {
	        f << l << endl;
	    }
	    f.close();
	}






	

};






#endif
