#include <fstream>
#include <algorithm>

#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix.hpp>
//#include <boost/numeric/ublas/assignment.hpp> //for easy initialization of vectors with <<=
#include <boost/numeric/ublas/io.hpp>
#include <boost/format.hpp>

#include <ctime>
#include <math.h> /*acos*/
#include <sstream> // for concatenating strings
#include <iomanip>

#include "simulation.hpp"

#include "defs.hpp"

#include <boost/algorithm/string.hpp>

namespace ublas = boost::numeric::ublas;
using namespace std;

Simulation::Simulation(unsigned int nParticles, double dt, double relGamma, double gamma0, double omega, int seed, string file, bool cont):
	 epot2(0),epot3(0), msd(0),
	 nParticles_(nParticles), t_(0), dt_(dt),
	 rng_(seed),rndm_(0,1),dice_(rng_,rndm_),
	 T_(0.001),gamma1_(1),gamma2_(0.5),
	 NN(1,4),GB(1,4),WBX(1,4),WBY(1,4),WBZ(1,4),RR(1,4),
	 posX(1,3),posY(1,3),posZ(1,3),
	 gamma0_(gamma0), omega_(omega),
	 stress(0), gStorage(0), gLoss(0),
	 t_startShear_(0),
	 nAv(40), eTotAveraged(0),
	 relGamma_(relGamma),
	 nCross(0),inFile(file), CONTINUE(cont),
	 cut_(0.05)
     {
     	string folder = "/usr/scratch1/fischer.andreas/results/runs/";
    	//string folder = "/home/andreas/polymer/results/";
     	outFolder = createTimeDir(folder);

	 	initStreams_();
	 	//initialize_();

	 	//inFile = "xonly_lc1.000000_L1.000000_xp6_nx1000.0_relGamma1_equilibrated.txt";

	 	//"xonly_lc1.000000_L1.000000_xp6_nx1000.0.txt";
	 	//"xonly_lc1.000000_L1.000000_xp6_nx1000.0_relD1_equilibrated.txt";
	 	//"xonly_lc1.000000_L1.000000_xp6_nx1000.0.txt";
	 	//"xonly_lc1.000000_L1.000000_xp6_nx1000.0_equilibrated.txt"
	 	//"xonly_lc1.000000_L1.000000_xp6_nx1000.0_relD0.001_equilibrated.txt";

	 	loadNetwork(file);

	 	double totFil=0;
	 	for(int i=0; i<NATOMS;i++)
	 	{
	 		for(int j=0; j<4;j++)
	 		{
	 			totFil+=RR(i,j);
	 		}
	 	}
	 	cout << "totFil1: " << totFil << "\n";

	 	if(CONTINUE==false)
	 	{
	 		correctRR_(0); // corrects RR=0 entries to a random entry between 0 and 0.1
	 	}

	 	totFil=0;
	 	for(int i=0; i<NATOMS;i++)
	 	{
	 		for(int j=0; j<4;j++)
	 		{
	 			totFil+=RR(i,j);
	 		}
	 	}
	 	cout << "totFil2: " << totFil << "\n";

	 	createCrosslinks();

	 	createPairList();

	 	createTripleList();

	 	totFil=0;

	 	for(unsigned int i=0; i<particlePairs2_.size(); i++)
	 	{
	 		totFil+=particlePairs2_.at(i).getContourLength();
	 	}
	 	cout << "totFil: " << totFil << "\t" << "nPairs:"<< particlePairs2_.size() << "\n";

	 	int NN2=0, NN3=0, NN4=0;
	 	int nn;
	 	for(int i=0; i<NATOMS;i++)
	 	{
	 		nn=0;
	 		for(int j=0; j<4;j++)
	 		{
	 			nn+=1-GB(i,j);
	 		}
	 		if(nn==2)
	 		{
	 			NN2+=1;
	 		}
	 		else if(nn==3)
	 		{
	 			NN3+=1;
	 		}
	 		else if(nn==4)
	 		{
	 			NN4+=1;
	 		}
	 	}
	 	cout << NN2 << "\t" << NN3 << "\t" << NN4 << "\n";

	 	//sampleCoordinates();
	 	sampleRR();

	 	cout << "continue: " << CONTINUE << "\n";

	 	cout << "gamma0: " << gamma0 << ", omega: " << omega << ", relGamma: " << relGamma_ << ", dt: " << dt << "\n";

	 	if (CONTINUE==false)
	 	{
	 		rngseed(seed);
	 	}
	 	else
	 	{
	 		rnginit(rIA, rpp, rp);
	 	}

	 	stringstream ss;
		ss << outFolder << "network" << "_str" << gamma0_ << "_w" << omega_ << "_dt" << dt_ << "_rg" << relGamma_ ;
		outFolderNW = ss.str();
		string sys_command = "mkdir " + outFolderNW;

		const char * comm = sys_command.c_str();

	 	system(comm);

	 	sampleNetwork();
	 	sampleTriples();
	 }

void Simulation::initStreams_() {
	stringstream ss;
	ss << "_str" << gamma0_ << "_w" << omega_ << "_dt" << dt_ << "_rg" << relGamma_ << "_cut" << cut_;
	string s = ss.str();

	string coordinateFile, energyFile, energyAvFile, rrFile, pairFile, tripleFile, debugFile, filLengthFile, angleFile, clDistFile;//, networkFile;
	coordinateFile=outFolder + "coord" + s +".dat";
	energyFile=outFolder + "energy" + s +".dat";
	energyAvFile= outFolder + "energyAv" + s +".dat";

	rrFile= outFolder + "rr" + s + ".dat";

	pairFile= outFolder + "pairs" + s + ".dat";
	tripleFile= outFolder + "triples" + s + ".dat";

	debugFile= outFolder + "debug" + s + ".dat";

	filLengthFile = outFolder + "filLength" + s + ".dat";

	angleFile = outFolder + "angles" + s + ".dat";

	clDistFile = outFolder + "clDist" + s + ".dat";

	//coordinatesStream_.open (coordinateFile);
  	//coordinatesStream_<< "# id \t r[0] \t r[1] \t r[2]" << endl;

	energyStream_.open(energyFile);
  	energyStream_<< "# t \t epot2 \t epot3  \t etot \t FXY \t stress2 \t stress3 \t stress \t ghostFilLength \t pairFilLength \t totFilLength" << endl;
  	energyStream_.precision(9);

    debugStream.open(debugFile);

    filLengthStream_.open(filLengthFile);

    angleStream_.open(angleFile);

  	rrStream_.open(rrFile);
  	rrStream_<< "# id \t RR0 \t RR1 \t RR2 \t RR3" << endl;

  	clDistStream_.open(clDistFile);

  	//pairStream_.open(pairFile);
  	//pairStream_ << "# id1 \t id2 \t l12 \t k2 \t Leq \t r12Abs" << endl;

   	tripleStream_.open(tripleFile);
  	tripleStream_ << "# id1 \t id2 \t id3 \t angle" << endl;

  	//energyAveragedStream_.open (energyAvFile);
  	//energyAveragedStream_ << "# t  \t etot \t FXY"<< endl;
  	//energyAveragedStream_.precision(9);
}

void Simulation::integrate(unsigned int nSteps) {
	ublas::vector<double> r(3,0);

	for(size_t i=1;i<=nSteps;i++) {

		/*if(i==1){
			t_startShear_=t_;
		}*/

		//if(i>400000){
		shear_();
			//cout << FXY << "\n";
		//}
		stress=0;
		stress2=0;
		stress3=0;

        force_(); // force calculation

        if (relGamma_>0){
        	calcPullProperties();
        }
        //cout << RR(10,3) << "\n";
        //if(i>50000){
        stress=stress2+stress3;
        //}

        sampleEnergy();

        vector<double> elong,springC,contLength,fPull,theta,clDist;
        ublas::vector<double> r1(3,0),r2(3,0);
        ublas::vector<int> WB(3,0);
		ublas::vector<double> FtimesWB(3,0);
        double r12Abs;
        if(i%1 == 0)
        {
        	for( unsigned int i=0; i<particlePairs2_.size(); i++)
        	{
        		//cout << particlePairs2_.size();
        		CrosslinkPair2 clpair = particlePairs2_.at(i);
        		elong.push_back(clpair.r12Abs-clpair.Leq);
        		springC.push_back(clpair.k2);
        		contLength.push_back(clpair.getContourLength());

        		r1=clpair.getCrosslink(1)->getPosition();
        		r2=clpair.getCrosslink(2)->getPosition();
        		WB[0]=clpair.getWBX(); WB[1]=clpair.getWBY(); WB[2]=clpair.getWBZ();
				FtimesWB[0]=FXX*WB[0]+FXY*WB[1]; FtimesWB[1]=FYY*WB[1]; FtimesWB[2]=FZZ*WB[2]; // F*WB (F=matrix, WB=vector)
				r2 += FtimesWB;
        		r12Abs=ublas::norm_2(r1-r2);
        		clDist.push_back(r12Abs);
	        }

	        for( unsigned int i=0;i<particleTriples2_.size(); i++)
	        {
	        	CrosslinkTriple2 cltriple = particleTriples2_.at(i);
	        	fPull.push_back(abs(cltriple.getfPull()));
	        	if(cltriple.getDummy()==0)
	        	{
	        		theta.push_back(cltriple.getAngle());
	        	}
	        }

      		debugStream << t_ << "\t" << *max_element(elong.begin(),elong.end()) << "\t" << *max_element(springC.begin(),springC.end()) << "\t" <<
      	    *min_element(contLength.begin(),contLength.end()) << "\t" << *max_element(contLength.begin(),contLength.end()) <<"\t" << *max_element(fPull.begin(),fPull.end()) << "\t" << epot2 << "\n";
    	}

        euler_(); // integrate equations of motion;

        t_ += dt_;
        //applyPeriodicBC_();

        if (i%1000 == 0)
        {
        	//sampleCoordinates();
        	sampleRR();
        	//sampleTriples();
        	//samplePairs();
        	sampleNetwork();
        	sampleFilLength(contLength);
        	sampleAngles(theta);
        	sampleClDist(clDist);
        	cout << t_ << "\n";
        	//cout << nCross << "\n";
        	//cout << FXY << "\n";
        	//compareForces_();

        }

        /*for(int i=0;i<NATOMS;i++)
        {
        	for(int j=0;j<4;j++)
        		if(RR(i,j)<0){cout << "some negative RR" << "\n";}
        }*/
        // check if arclength is conserved
        /*if (i%100 == 0){
        double RRSUM=0;
	        for(int i=0; i<NATOMS;i++)
	        {
	        	for(int j=0; j<4;j++)
	        	{
	        		RRSUM+=RR(i,j);
	        	}
	        }
	        cout << RRSUM << "\n";
	    }*/
	}

}

void Simulation::resetForce_() {
	ublas::vector<double> f(3,0);
	for (std::vector<Crosslink>::iterator particlei = particles_.begin(); particlei != particles_.end(); ++particlei)
	{
		particlei->updateForce(f);
		particlei->updateFilForce(f,1);
		particlei->updateFilForce(f,2);
		//particlei->filament1_.updateForce(f);
		//particlei->filament2_.updateForce(f);
	}
}

void Simulation::force_() {
	resetForce_(); //sets force to zero;

	epot2=0;
	epot3=0;

	//for (std::vector<Crosslink>::iterator particlei = particles_.begin(); particlei != particles_.end(); ++particlei)
	for ( unsigned int i=0; i<particlePairs2_.size(); i++)
	{

		force2_(particlePairs2_.at(i));

	}
	for ( unsigned int i=0; i<particleTriples2_.size(); i++)
	{
		force3_(particleTriples2_.at(i));

	}
}

void Simulation::force2_(CrosslinkPair2 &clPair){

	ublas::vector<double> r1(3,0),r2(3,0);
	ublas::vector<double> f1(3,0),f2(3,0);
	ublas::vector<double> r12(3,0);
	double r12Abs=0;
	double epotPair=0;
	ublas::vector<int> WB(3,0);
	ublas::vector<double> FtimesWB(3,0);
	int filId1, filId2;

	double l12; // length of polymer between cl 1 and 2
	double k2; // spring constant
	double Leq; //equilibrium length

	Crosslink *cl1 = clPair.getCrosslink(1);
	Crosslink *cl2 = clPair.getCrosslink(2);
	//int filId = clPair.getFilId();

	r1=cl1->getPosition();
	r2=cl2->getPosition();
	//!!!!periodic boundary conditions!!!!
	WB[0]=clPair.getWBX(); WB[1]=clPair.getWBY(); WB[2]=clPair.getWBZ();

	FtimesWB[0]=FXX*WB[0]+FXY*WB[1]; FtimesWB[1]=FYY*WB[1]; FtimesWB[2]=FZZ*WB[2]; // F*WB (F=matrix, WB=vector)
	//cout << FtimesWB << "\n";
	r2 += FtimesWB;
	//!!!!!//

	r12 = r2-r1;
	r12Abs=ublas::norm_2(r12);
	//calculate k2 and Leq
	l12=clPair.getContourLength();
	k2=90./pow(l12,4);
	Leq=l12*(1-l12/6.);

	clPair.k2=k2;
	clPair.r12Abs=r12Abs;
	clPair.Leq=Leq;

	// pair force: harmonic spring with spring constant k2
	f1=k2*(r12Abs-Leq)*r12/r12Abs;
	f2=-f1;

	clPair.f1=ublas::norm_2(f1);

	filId1=clPair.getFilId(1);
	filId2=clPair.getFilId(2);

	cl2->incrForce(f2,filId2);

	//cl2->incrForceFil(f,filId);
	cl1->incrForce(f1,filId1);
	//cl1->incrForceFil(-1*f,filId);

	epotPair=0.5*k2*pow(r12Abs-Leq,2);
	clPair.updateEpotPair(epotPair);
	epot2+=epotPair;

	//cout << "k2: " << k2 << "  Leq: " << Leq << "    deltaRabs: " << deltaRabs << "    f: " << f << "\n";

	// compute stress sigma_xy=- (r2[0]-r1[0])*f1[1]
	stress2+=r12[0]*f1[1];
}

void Simulation::force3_(CrosslinkTriple2 &clTriple){

	int iDummy;
	double angle;//,r12abs,r13abs,r23abs;
	ublas::vector<double> f1(3,0),f2(3,0),f3(3,0);
	ublas::vector<double> r1(3,0),r2(3,0),r3(3,0);
	ublas::vector<double> r12(3,0),r23(3,0),e12(3,0),e23(3,0);
	double r12abs, r23abs;
	double epotTriple=0;
	ublas::vector<int> WB12(3,0),WB23(3,0);
	ublas::vector<double> FtimesWB12(3,0);
	ublas::vector<double> FtimesWB23(3,0);

	double l12,l23; // contour lengths between 1,2 and 2,3
	double k3; // prefactor of force

	int filId1,filId2,filId3;

	double pref;
	double angTol=M_PI/100.;

	Crosslink *cl1 = clTriple.getCrosslink(1);
	Crosslink *cl2 = clTriple.getCrosslink(2);
	Crosslink *cl3 = clTriple.getCrosslink(3);

	//int filId = clTriple.getFilId();

	iDummy=clTriple.getDummy();
	if(iDummy==0) // 3-cl force only if no dummies in triple
	{
		r1=cl1->getPosition();
		r2=cl2->getPosition();
		r3=cl3->getPosition();

		///!!!! periodic boundary conditions !!!!
		WB12[0]=clTriple.getWBX(12); WB12[1]=clTriple.getWBY(12); WB12[2]=clTriple.getWBZ(12);
		WB23[0]=clTriple.getWBX(23); WB23[1]=clTriple.getWBY(23); WB23[2]=clTriple.getWBZ(23);

		FtimesWB12[0]=FXX*WB12[0]+FXY*WB12[1]; FtimesWB12[1]=FYY*WB12[1]; FtimesWB12[2]=FZZ*WB12[2]; // F*WB (F=matrix, WB=vector)
		FtimesWB23[0]=FXX*WB23[0]+FXY*WB23[1]; FtimesWB23[1]=FYY*WB23[1]; FtimesWB23[2]=FZZ*WB23[2]; // F*WB (F=matrix, WB=vector)

		r1+=FtimesWB12; //was r2!!
		r3+=FtimesWB23;
		///!!!

		r12=r2-r1;
		r23=r3-r2;
		//cout << ublas::inner_prod(r12,r23) << "\n";
		r12abs=ublas::norm_2(r12);
		r23abs=ublas::norm_2(r23);

		angle= acos(ublas::inner_prod(r12,r23)/(r12abs*r23abs));
		//cout << acos(1) << "\n";

		l12=clTriple.getContourLength(12);
		l23=clTriple.getContourLength(23);

		k3=1.5/(l12+l23);

		if(angle>angTol)
		{  // need this condition, because for angle=0, divison by 0 (sin(0)=0)
			pref=angle/sin(angle);
		}
		else //taylor expand sin(angle) for small angles
		{
			pref=1./(1-1./6.*angle*angle+1./120.*angle*angle*angle*angle-1./5040.*angle*angle*angle*angle*angle*angle);
		}

		e12=r12/r12abs;
		e23=r23/r23abs;

		f1=(-e23+cos(angle)*e12)/r12abs;
		f3=(e12-cos(angle)*e23)/r23abs;
		//f2=k3*2*pref/r12abs/r23abs*(r23-r12-cos(angle)*(r12/r12abs-r23/r23abs));
		f2=(e23-cos(angle)*e12)/r12abs+(e23*cos(angle)-e12)/r23abs; //10.03.2016

		f1=f1*k3*2*pref;
		f2=f2*k3*2*pref;
		f3=f3*k3*2*pref;
		//cout << f1 << "\t" << f2 << "\t" << f3 << "\n";

		// new forces (!!!WRONG!!!)
		//f1=-k3*(r12/r12abs/pow((r12abs+r23abs),2)*angle*angle-2*angle/sin(angle)/r12abs*(-r23/r23abs+cos(angle)*r12/r12abs));
		//f3=-k3*(r23/r23abs/pow((r12abs+r23abs),2)*angle*angle-2*angle/sin(angle)/r23abs*(r12/r12abs-cos(angle)*r23/r23abs));
		//f2=-k3*((r23/r23abs-r12/r12abs)/pow((r12abs+r23abs),2)*angle*angle-2*angle/sin(angle)/r12abs/r23abs*(r23-r12-cos(angle)*(r12/r12abs-r23/r23abs)));

		filId1=clTriple.getFilId(1);
		filId2=clTriple.getFilId(2);
		filId3=clTriple.getFilId(3);

		cl1->incrForce(f1,filId1);
		//cl1->incrForceFil(f1,filId);
		cl2->incrForce(f2,filId2);
		//cl2->incrForceFil(f2,filId);
		cl3->incrForce(f3,filId3);
		//cl3->incrForceFil(f3,filId);

		//epotTriple=k3/(r12abs+r23abs)*angle*angle;
		epotTriple=k3*angle*angle;

		clTriple.updateEpotTriple(epotTriple);
		clTriple.updateAngle(angle);
		epot3+=epotTriple;
		//cout << angle << "\n";

		// compute stress sigma_xy=-((r1[0]-r2[0])*f1[1]+(r3[0]-r2[0])*f3[1]), WRONG!! virial stress is zero for 3-body forces that depend on bond angle only
		stress3+= (r2[0]-r1[0])*f1[1]+(r2[0]-r3[0])*f3[1];
	}
}

void Simulation::euler_() {
	ublas::vector<double>  noise(3,0);
	double noise2;
	double ds;
	double sigma1;
	double sigma2=sqrt(2*dt_);//*T_/gamma1_);
	double l12, l23;
	ublas::vector<double>  r1(3,0),r2(3,0),r3(3,0);
	double r12,r23;
	int iDummy;
	//double cut=0.05;

	if(relGamma_!=-1)
	{
		int d=0;
		for (std::vector<CrosslinkTriple2> ::iterator triplei = particleTriples2_.begin(); triplei != particleTriples2_.end(); triplei++)
		{
			iDummy=triplei->getDummy();

			//if(triplei->getDummy() == 0){
			//noise2=dice_();
			noise2=gaussian_random_number_();
			ds=dt_*triplei->getfPull()+sigma2*noise2;
			//cout << ds << "\n";

			// FOR DEBUGGING ONLY
			r1=triplei->getCrosslink(1)->getPosition();
			r2=triplei->getCrosslink(2)->getPosition();
			r3=triplei->getCrosslink(3)->getPosition();
			r12=ublas::norm_2(r1-r2);
			r23=ublas::norm_2(r3-r2);
			// FOR DEBUGGING ONLY
			l12=triplei->getContourLength(12);
			l23=triplei->getContourLength(23);
			//cout << l12 << "\t" << l23 << "\n";
			//if( (l12+ds >= r12) && (l23-ds >= r23))
			//{

			if(iDummy==0)
			{
				//bool a = (l23-ds>cut_) && (l12+ds>=cut_);

				//cout << triplei->getCrosslink(1)->getId() << "\t"<< triplei->getCrosslink(2)->getId()<< "\t" << triplei->getCrosslink(3)->getId() << "\t" << a << "\t" << l12 << "\t" << l23 << "\t" << ds << "\n";
				if( (l12+ds>cut_) && (l23-ds>cut_) ) // contour length between two crosslinks must be > cut_
				{
					triplei->incrContourLength(ds,12);
					triplei->incrContourLength(-ds,23);
				}
			}
			if(iDummy==1)
			{
				//bool a = (l23-ds>cut_) && (l12+ds>=0);
				//cout << triplei->getCrosslink(1)->getId() << "\t"<< triplei->getCrosslink(2)->getId()<< "\t" << triplei->getCrosslink(3)->getId() << "\t" << a << "\t" << l12 << "\t" << l23 << "\t" << ds << "\n";
				if((l23-ds>cut_) && (l12+ds>=0) )
				{
					//cout << l12+ds << "\n";
					//cout << l23-ds << "\n";
					triplei->incrContourLength(ds,12);
					triplei->incrContourLength(-ds,23);
					//cout << triplei->getContourLength(12) << "\n";
					//cout << triplei->getContourLength(23) << "\n";
				}
			}

			if(iDummy==3)
			{
				if( (l12+ds>cut_) && (l23-ds>=0))
				{
					triplei->incrContourLength(ds,12);
					triplei->incrContourLength(-ds,23);
				}
			}

			l12=triplei->getContourLength(12);
			l23=triplei->getContourLength(23);

			if(l12 < 0 || l23 <0){cout << "some RR smaller cut" << "\t" << ds << "\n";}
			if( (l12 < r12) || (l23 < r23))
			{
				nCross++;
			}
		//}

		}
	}

	int id;
	ublas::vector<double> dr,r,rN;
	double drN;
	int check;

	double rG;

	for (std::vector<Crosslink>::iterator particlei = particles_.begin(); particlei != particles_.end(); particlei++)
	{
		check=1;
		noise=gaussian_random_vector2_();

		if(relGamma_==-1){rG=1;}
		else{rG=relGamma_;}

		sigma1=sqrt(2*rG*dt_);

		dr=	rG*dt_*particlei->getForce()+sigma1*noise;

		//cout << sigma1*noise << "\t" << noise << "\n";
		//cout << relGamma_*dt_*particlei->getForce() << "\n";
		// check whether step doesn't bring cls too close
		id=particlei->getId();
		r=particlei->getPosition();
		for(int i=0;i<4;i++)
		{
			if(GB(id,i)==0)
			{
				rN=particles_.at(NN(id,i)).getPosition();
				drN=ublas::norm_2(rN-(r+dr));
				//cout << drN << "\n";
				if(drN<0.001)
				{
					check=0;

				}
			}
		}
		if(check==1)
		{
			particlei->incrPosition(dr);
		}

		//particlei->incrPosition(sigma1*noise);
		//DeltaS1=1/gamma*Force1+noise1;
		//DeltaS2=1/gamma*Force2+noise2;
		//DeltaR=0.5*(DeltaS1*particlei->filament1_.getPullDirection()+DeltaS2*particlei->filament2_.getPullDirection());
		//particlei->incrPosition(deltaR);
	}
}

void Simulation::applyPeriodicBC_(){
	ublas::vector<double> r(3,0);
	ublas::matrix<int> conTabSelf;
	ublas::matrix<int> conTabOther;
	int k,l;
	int deltaWBX, deltaWBY, deltaWBZ;
	ublas::vector<double> eX(3), eY(3), eZ(3);
	eX[0]=1;eX[1]=0;ex[2]=0; eY[0]=0;eY[1]=1;eY[2]=0; eZ[0]=0;eZ[1]=0;eZ[2]=1;
	double dxPbc, dyPbc, dzPbc;
	ublas::vector<double> drPbc(3,0);

	for (std::vector<Crosslink>::iterator particlei = particles_.begin(); particlei != particles_.end(); particlei++)
	{
		deltaWBX=0; deltaWBY=0; deltaWBZ=0;
		dxPbc=0; dyPbc=0; dzPbc=0;
		r=particlei->getPosition();
		if(r[0] > 0.5*L || r[0] < -0.5*L || r[1] > 0.5*L || r[1] < -0.5*L || r[2] > 0.5*L || r[2] < -0.5*L)
		{
			conTabSelf=particlei->getConTabSelf();
			conTabOther=particlei->getConTabOther();
			if(r[0]>0.5*L){
				deltaWBX=1;
				dxPbc= -L;
			}
			else if(r[0]<-0.5*L){
				deltaWBX=-1;
				dxPbc=L;
			}
			if(r[1]>0.5*L){
				deltaWBY=1;
				dyPbc= -L;
			}
			else if(r[1]<-0.5*L){
				deltaWBY=-1;
				dyPbc=L;
			}
			if(r[2]>0.5*L){
				deltaWBZ=1;
				dzPbc = -L;
			}
			else if(r[2]<-0.5*L){
				deltaWBZ=-1;
				dzPbc = L;
			}
			drPbc <<= dxPbc, dyPbc, dzPbc;
			particlei->incrPosition(drPbc);

			for(int i=0; i<conTabSelf.size1(); i++){
				k=conTabSelf(i,0);
				l=conTabSelf(i,1);
				WBX(k,l) -= deltaWBX;
				WBY(k,l) -= deltaWBY;
				WBZ(k,l) -= deltaWBZ;
				if(abs(WBX(k,l))>1 || abs(WBY(k,l)) > 1 || abs(WBZ(k,l)) > 1){
					cout << "some abs(WB) > 2, probably something went wrong!!" <<"\n";
				}
			}

			for(int i=0; i<conTabOther.size1(); i++){
				k=conTabOther(i,0);
				l=conTabOther(i,1);
				WBX(k,l) += deltaWBX;
				WBY(k,l) += deltaWBY;
				WBZ(k,l) += deltaWBZ;
				if(abs(WBX(k,l))>1 || abs(WBY(k,l)) > 1 || abs(WBZ(k,l)) > 1){
					cout << "some abs(WB) > 2, probably something went wrong!!" <<"\n";
				}
			}
		}
	}
}

void Simulation::sampleCoordinates() {
	ublas::vector<double> r(3);//, forceFil1(3), forceFil2(3), force(3), pullDir1(3), pullDir2(3);
	int id;//,filId1,filId2,filClId11,filClId12,filClId21,filClId22;
	//double pullForce1,pullForce2;
	//Crosslink* particlei;
	coordinatesStream_ << "# t=" << t_ << "##################" << "\n";
	for (std::vector<Crosslink>::iterator particlei = particles_.begin(); particlei != particles_.end(); particlei++)
	{
		id = particlei->getId();
		r = particlei->getPosition();
		//coordinatesStream_ << id << "\t" << r[0] << "\t" << r[1] << "\t" << r[2] << "\n";
		coordinatesStream_ << r[0] << "\t" << r[1] << "\t" << r[2] << "\n";
		/*if(abs(r[0])>0.5 || abs(r[1])>0.5 || abs(r[2])>0.5){
			cout << "some coordinate outside box --> problem with periodic boundaries" << "\n";
		}*/
	}
}

void Simulation::sampleEnergy() {
	double ghostFilLength=0, pairFilLength=0, totFilLength=0;
	for(int i=0;i<NATOMS;i++)
	{
		for(int j=0;j<4;j++)
		{
			if(GB(i,j)==1)
			{
				ghostFilLength += RR(i,j);
			}
			else
			{
				pairFilLength += RR(i,j);
			}
		}
	}
	pairFilLength/=2.;

	totFilLength=ghostFilLength+pairFilLength;

	energyStream_ << t_ << "\t" << epot2 << "\t" << epot3 << "\t" << epot2+epot3 << "\t" << FXY << "\t" << stress2 << "\t" << stress3 << "\t" << stress << "\t" << ghostFilLength << "\t" << pairFilLength << "\t" << totFilLength << endl;
	//if(i % nAv == 0){
	//energyAveragedStream_ << t_ << "\t" << epot2+epot3 << "\t" << FXY << endl;
	//}
}

void Simulation::sampleTriples() {
	int id1,id2,id3;
	double angle;
	int iDummy;

	for (std::vector<CrosslinkTriple2>::iterator triplei = particleTriples2_.begin(); triplei != particleTriples2_.end(); triplei++)
	{
		iDummy=triplei->getDummy();

		//if(iDummy==0)
		//{
			id1=triplei->getCrosslink(1)->getId();id2=triplei->getCrosslink(2)->getId();id3=triplei->getCrosslink(3)->getId();
			angle=triplei->getAngle();
			tripleStream_ << t_ << "\t" << id1 << "\t" << id2 << "\t" << id3 << "\t" << angle << "\t" << triplei->getContourLength(12) << "\t" << triplei->getContourLength(23) << endl;
		//}
	}
}

void Simulation::samplePairs() {
	int id1,id2;
	pairStream_ << "# t=" << t_ << "##################" << "\n";
	for (std::vector<CrosslinkPair2>::iterator pairi = particlePairs2_.begin(); pairi != particlePairs2_.end(); pairi++)
	{
		id1=pairi->getCrosslink(1)->getId();id2=pairi->getCrosslink(2)->getId();
		pairStream_ << id1 << "\t" << id2 << "\t" << pairi->getContourLength() << "\t" << pairi->k2 << "\t" << pairi->r12Abs - pairi->Leq << "\t"
		<< pairi->f1 << "\t" << pairi->Leq << "\t" << pairi-> r12Abs << endl;
	}
}

void Simulation::sampleRR() {
	rrStream_ << "#t=" << t_ << "##################" << "\n";
	for(int i=0;i<NATOMS;i++)
	{
		rrStream_ << RR(i,0) << "\t" << RR(i,1) << "\t" << RR(i,2) << "\t" << RR(i,3) << endl;
	}
}

void Simulation::sampleFilLength(vector<double> contLength)
{
	filLengthStream_ << "#t=" << t_ << "###############" << endl;
	double binSize=0.005;
	int nBins=(int) (1./binSize);
	//cout << nBins << "\n";
	double xHist [nBins];
	for(int i=0;i<nBins;i++)
	{
		xHist[i] = (i+1)*binSize;
	}
	int yHist [nBins];
	for(int i=0;i<nBins;i++)
	{
		yHist[i]=0;
	}
	int n;

	for (int i=0; i<contLength.size(); i++)
	{
		n=floor(contLength[i]/binSize);
		yHist[n]++;
	}

	for(int i=0;i<nBins;i++)
	{
		filLengthStream_ << xHist[i] << "\t" << yHist[i] << endl;
	}

}

void Simulation::sampleAngles(vector<double> theta)
{
	angleStream_ << "#t=" << t_ << "###############" << endl;

	double binSize = 0.02;
	int nBins = (int) (1.6/binSize);

	double xHist [nBins];
	for(int i=0;i<nBins;i++)
	{
		xHist[i] = (i+1)*binSize;
	}
	int yHist [nBins];
	for(int i=0;i<nBins;i++)
	{
		yHist[i]=0;
	}
	int n;

	for (int i=0; i<theta.size(); i++)
	{
		n=floor(theta[i]/binSize);
		yHist[n]++;
	}

	for(int i=0;i<nBins;i++)
	{
		angleStream_ << xHist[i] << "\t" << yHist[i] << endl;
	}
}

void Simulation::sampleClDist(vector<double> clDist)
{
	clDistStream_ << "#t=" << t_ << "###############" << endl;

	double binSize = 0.005;
	int nBins = (int) (1./binSize);

	double xHist [nBins];
	for(int i=0;i<nBins;i++)
	{
		xHist[i] = (i+1)*binSize;
	}
	int yHist [nBins];
	for(int i=0;i<nBins;i++)
	{
		yHist[i]=0;
	}
	int n;

	for (int i=0; i<clDist.size(); i++)
	{
		n=floor(clDist[i]/binSize);
		yHist[n]++;
	}

	for(int i=0;i<nBins;i++)
	{
		clDistStream_ << xHist[i] << "\t" << yHist[i] << endl;
	}
}

void Simulation::sampleNetwork()
{
	string outFile;

	stringstream ss;

	//inFile = "xonly_lc1.000000_L1.000000_xp6_nx1000.0_relGamma1_equilibrated.txt";

	ss.precision(9);

	size_t txtPos = inFile.find(".txt");
	string nw_str = inFile.substr(0,txtPos);

	ss << nw_str << "_t" << fixed << t_;
	ss.precision(4);
	ss << defaultfloat << "_str" << gamma0_ << "_w" << defaultfloat << omega_ << "_dt" << dt_ << "_rg" << relGamma_ ;
	string s = ss.str();

	outFile= outFolderNW + "/" +  s + ".dat";
	networkStream_.open(outFile);

	//networkStream_ << "###############################" << endl;
	//networkStream_ << defaultfloat << "#t=" << t_  << endl;

	networkStream_ << "#NATOMS=" << dec << NATOMS << endl;
	networkStream_ << "#FEC=1" << endl;

	networkStream_<<  "#FXX=" << std::hexfloat << FXX << endl;
	networkStream_<< "#FYY=" <<  FYY << endl;
	networkStream_<< "#FZZ=" <<  FZZ << endl;
	networkStream_<< "#FXY=" <<  FXY << endl;
	networkStream_<< "#FXZ=" <<  FXZ << endl;
	networkStream_<< "#FYZ=" <<  FYZ << endl;
	networkStream_<< "#FYX=" <<  FYX << endl;
	networkStream_<< "#FZX=" <<  FZX << endl;
	networkStream_<< "#FZY=" <<  FZY << endl;
	networkStream_<< "Lp=" << dec << Lp << endl;
	networkStream_<< "#BENDING_PF=0x1.8p+0" << endl;
	networkStream_<< "#StretchEps=0x1.0c6f7a0b5ed8dp-20" << endl;
	networkStream_<< "#rp=" << rng_p << endl;
	networkStream_<< "#rpp=" << rng_pp << endl;

	networkStream_<< "#rIA=";
	for(int i=0; i<54;i++)
	{
		networkStream_<< rng_ia[i] << " ";
	}
	networkStream_<< rng_ia[54] << endl;

	ublas::vector<double> r(3,0);

	for (std::vector<Crosslink>::iterator particlei = particles_.begin(); particlei != particles_.end(); particlei++)
	{
		r = particlei->getPosition();
		networkStream_ << hexfloat << r[0] << "\t" << r[1] << "\t" << r[2] << "\n";
	}

	for(int i=0;i<NATOMS;i++)
	{
		networkStream_ << dec << NN(i,0) << " " << NN(i,1) << " " << NN(i,2) << " " << NN(i,3) << endl;
	}

	for(int i=0;i<NATOMS;i++)
	{
		networkStream_ << dec << GB(i,0) << " " << GB(i,1) << " " << GB(i,2) << " " << GB(i,3) << endl;
	}

	for(int i=0;i<NATOMS;i++)
	{
		networkStream_ << hexfloat << RR(i,0) << " " << RR(i,1) << " " << RR(i,2) << " " << RR(i,3) << endl;
	}

	for(int i=0;i<NATOMS;i++)
	{
		networkStream_ << dec << WBX(i,0) << " " << WBX(i,1) << " " << WBX(i,2) << " " << WBX(i,3) << endl;
	}

	for(int i=0;i<NATOMS;i++)
	{
		networkStream_ << dec << WBY(i,0) << " " << WBY(i,1) << " " << WBY(i,2) << " " << WBY(i,3) << endl;
	}

	for(int i=0;i<NATOMS;i++)
	{
		networkStream_ << dec << WBZ(i,0) << " " << WBZ(i,1) << " " << WBZ(i,2) << " " << WBZ(i,3) << endl;
	}

	networkStream_.close();

}

/* void Simulation::gaussian_random_number_(){
	ublas::vector<double> randVec(3,0);
    double f1,f2,p;

    //uniform_real_distribution<double> dis(0.0, 1.0);

    //f1=dis(generator_);
    //f2=dis(generator_);

    f1=dice_();
    f2=dice_();
    p=sqrt(-2.*log(f1));

    randVec[0]=p*cos(2.*M_PI*f2);
    randVec[1]=p*sin(2.*M_PI*f2);

    //rand_no1_=p*cos(2.*M_PI*f2);
    //rand_no2_=p*sin(2.*M_PI*f2);
    // randomStream << rand_no[1] << "\n";
    // randomStream << rand_no[2] << "\n";
}*/

ublas::vector<double> Simulation::gaussian_random_vector_(){
	ublas::vector<double> randVector(3,0);
	randVector[0]=dice_();
	randVector[1]=dice_();
	randVector[2]=dice_();
	return randVector;
}

 ublas::vector<double> Simulation::gaussian_random_vector2_(){

 	ublas::vector<double> randVec(3,0);

     double f1,f2,f3,f4,p1,p2;

     //f1=dice_();
     f1=rngmit;
     //f2=dice_();
     f2=rngmit;

     p1=sqrt(-2.*log(f1));

     f3=rngmit;
     //f2=dice_();
     f4=rngmit;

     p2=sqrt(-2.*log(f3));

     randVec[0]=p1*cos(2.*M_PI*f2);

     randVec[1]=p1*sin(2.*M_PI*f2);

     randVec[2]=p2*cos(2.*M_PI*f4);

     //cout << randVec << "\n";

     return randVec;

     //rand_no1_=p*cos(2.*M_PI*f2);

     //rand_no2_=p*sin(2.*M_PI*f2);

     // randomStream << rand_no[1] << "\n";

     // randomStream << rand_no[2] << "\n";

 }

 double Simulation::gaussian_random_number_()
 {
 	double f1,f2,p;
 	double randNo;

 	f1=rngmit;
 	f2=rngmit;

 	p=sqrt(-2.*log(f1));

 	randNo=p*cos(2.*M_PI*f2);

 	return randNo;
 }

void Simulation::mean_square_displacement(){
	ublas::vector<double> r(3),deltaR(3),r0(3,1);
	double msd1;
	int i=0;
	for (std::vector<Crosslink>::iterator particle = particles_.begin(); particle != particles_.end(); particle++)
	{
		i++;
		r = particle->getPosition();
		deltaR = r-i*r0;
		msd1=pow(ublas::norm_2(deltaR),2);
		msd+=msd1;
	}
	msd=msd/3000;
}

void Simulation::calcPullProperties(){
	ublas::vector<double> r1(3,0),r2(3,0),r3(3,0);
	ublas::vector<double> r21(3,0),r23(3,0),r13(3,0);
	ublas::vector<double> e21(3,0),e23(3,0),e13(3,0);
	ublas::vector<int> WB12(3,0),WB23(3,0);
	ublas::vector<double> FtimesWB12(3,0);
	ublas::vector<double> FtimesWB23(3,0);
	ublas::vector<double> f2(3,0);
	double f21,f23, f13;
	double pullforce;
	int iDummy;
	int filId2;
	ublas::vector<double> fOtherFil(3,0);

	for (std::vector<CrosslinkTriple2> ::iterator clTriple = particleTriples2_.begin(); clTriple != particleTriples2_.end(); clTriple++)
	{
		Crosslink *cl1 = clTriple->getCrosslink(1);
		Crosslink *cl2 = clTriple->getCrosslink(2);
		Crosslink *cl3 = clTriple->getCrosslink(3);

		r1=cl1->getPosition();
		r2=cl2->getPosition();
		r3=cl3->getPosition();

		iDummy=clTriple->getDummy();

		//if(iDummy==0){
		///!!!! periodic boundary conditions !!!!
		WB12[0]=clTriple->getWBX(12); WB12[1]=clTriple->getWBY(12); WB12[2]=clTriple->getWBZ(12);
		WB23[0]=clTriple->getWBX(23); WB23[1]=clTriple->getWBY(23); WB23[2]=clTriple->getWBZ(23);

		FtimesWB12[0]=FXX*WB12[0]+FXY*WB12[1]; FtimesWB12[1]=FYY*WB12[1]; FtimesWB12[2]=FZZ*WB12[2]; // F*WB (F=matrix, WB=vector)
		FtimesWB23[0]=FXX*WB23[0]+FXY*WB23[1]; FtimesWB23[1]=FYY*WB23[1]; FtimesWB23[2]=FZZ*WB23[2]; // F*WB (F=matrix, WB=vector)

		r1+=FtimesWB12; // was r2!!
		r3+=FtimesWB23;

		// !!! WHEN DUMMY TRIPLE, TAKE CARE!!

		r21=r1-r2;
		r23=r3-r2;

		if(iDummy==0)
		{
			e21=r21/ublas::norm_2(r21);
			e23=r23/ublas::norm_2(r23);
		}
		// set projection direction for dummy crosslinks
		else if(iDummy==1)
		{
			e23=r23/ublas::norm_2(r23);
			e21=-e23;
		}
		else if(iDummy==3)
		{
			e21=r21/ublas::norm_2(r21);
			e23=-e21;
		}
		// !!! CHANGE TO FORCE FROM OTHER FILAMENT !!!
		filId2=clTriple->getFilId(2);

		if(filId2==1)
		{
			fOtherFil=(cl2->getFilForce(2)-cl2->getFilForce(1))*0.5;
			//cout << fOtherFil << "\t" << cl2->getFilForce(2) << "\t" << cl2->getFilForce(1) << "\t" << cl2->getForce() << "\n";

		}
		else if(filId2==2)
		{
			fOtherFil=(cl2->getFilForce(1)-cl2->getFilForce(2))*0.5;
		}
		else
		{
			cout << "filId Error3" << "\n";
		}
		//fOtherFil=cl2->getForce();
		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

		r13=r3-r1;
		e13=r13/ublas::norm_2(r13);

		f13=ublas::inner_prod(fOtherFil,e13);

		f21=ublas::inner_prod(fOtherFil,e21);  // project pull force
		f23=ublas::inner_prod(fOtherFil,e23);

		pullforce=f13;

		/*if(f23>f21){
			//pullforce=f23*1;
			pullforce=ublas::norm_2(fOtherFil);
		}
		else{
			//pullforce=f21*(-1);
			pullforce=ublas::norm_2(fOtherFil)*(-1);
		}*/

		clTriple->updatefPull(pullforce);
		///!! case f23==f21 !! missing
		/*if(iDummy==0)
		{
			cout << pullforce << "\n";
		}*/
	//}
	}

}

void Simulation::loadNetwork(string file){

	// read Lc, L from filename
	size_t lcPos = file.find("lc");
	string Lc_str = file.substr(lcPos+2,8);
	Lc = stod(Lc_str); // convert string to double
	cout << Lc << "\n";

	size_t LPos = file.find("L");
	string L_str = file.substr(LPos+1,8);
	L = stod(L_str);
	cout << L << "\n";

	if(CONTINUE==1){
		size_t tPos = file.find("_t");
		size_t strPos = file.find("_str");
		string t_str = file.substr(tPos+2,strPos-tPos);
		t_=stod(t_str);
		cout << t_ << "\n";
	}

	// read parameters from file
	ifstream infile(file);
	string str;

	str=readNumber(infile);
	NATOMS = stoi(str);  // convert string to int
	cout << NATOMS << "\n";
	str=readNumber(infile);
	FEC=stoi(str);
	cout << FEC << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &FXX); // convert hexa-string to double
	cout << FXX << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &FYY);
	cout << FYY << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &FZZ);
	cout << FZZ << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &FXY);
	cout << FXY << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &FXZ);
	cout << FXZ << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &FYX);
	cout << FYX << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &FYZ);
	cout << FYZ << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &FZX);
	cout << FZX << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &FZY);
	cout << FZY << "\n";
	str=readNumber(infile);
	//Lp=stod(str);
	::sscanf(str.c_str(), "%lA", &Lp);
	cout << Lp << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &BENDING_PF);
	cout << BENDING_PF << "\n";
	str=readNumber(infile);
	::sscanf(str.c_str(), "%lA", &StretchEps);
	cout << StretchEps << "\n";
	str=readNumber(infile);
	rp=stoi(str);
	cout << rp << "\n";
	str=readNumber(infile);
	rpp=stoi(str);
	cout << rpp << "\n";

	str=readNumber(infile);
	cout << str << "\n";
	vector<string> strs;
  	boost::split(strs,str,boost::is_any_of(" "));
  	const char * c;
  	for (size_t i = 0; i < strs.size(); i++){
  		c = strs[i].c_str();
  		rIA[i]=strtoul(c,NULL,10);
  		//cout << rIA[i] << endl;
  	}
    //

	//size_t spacePos=str.find(" ");
	//strtoul

	//getline(infile,str); // not reading rIA yet!!!

	posX.resize(NATOMS,0); posY.resize(NATOMS,0); posZ.resize(NATOMS,0);

	string posX_hex,posY_hex,posZ_hex;

	NN.resize(NATOMS,4);
	GB.resize(NATOMS,4);
	RR.resize(NATOMS,4);
	WBX.resize(NATOMS,4);WBY.resize(NATOMS,4);WBZ.resize(NATOMS,4);

	string RR1_hex, RR2_hex, RR3_hex, RR4_hex;

	for(int i=0; i<NATOMS; i++){
		infile >> posX_hex >> posY_hex >> posZ_hex;
		//cout << posX_hex << "\t" <<  posY_hex << "\t" << posZ_hex << "\n";

		//posX(i)=stod(posX_hex);
		//posY(i)=stod(posY_hex);
		//posZ(i)=stod(posZ_hex);

		::sscanf(posX_hex.c_str(), "%lA", &posX(i));
		::sscanf(posY_hex.c_str(), "%lA", &posY(i));
		::sscanf(posZ_hex.c_str(), "%lA", &posZ(i));

		//cout << posX(i) << "\t" <<  posY(i) << "\t" << posZ(i) << "\n";
	}
	for(int i=0; i<NATOMS; i++){
		infile >> NN(i,0) >> NN(i,1) >> NN(i,2) >> NN(i,3);

		//cout << NN(i,0) << "\t" << NN(i,1) << "\t" << NN(i,2) << "\t" << NN(i,3) << "\n";
	}

	for(int i=0; i<NATOMS; i++){
		infile >>  GB(i,0) >> GB(i,1) >> GB(i,2) >> GB(i,3);

		//cout << GB(i,0) << "\t" << GB(i,1) << "\t" << GB(i,2) << "\t" << GB(i,3) << "\n";
	}

	for(int i=0; i<NATOMS; i++){
		infile >> RR1_hex >> RR2_hex >> RR3_hex >> RR4_hex;

		//cout << RR1_hex << "\t" << RR2_hex<< "\t" << RR3_hex << "\t" << RR4_hex << "\n";
		//RR(i,0)=stod(RR1_hex);
		//RR(i,1)=stod(RR2_hex);
		//RR(i,2)=stod(RR3_hex);
		//RR(i,3)=stod(RR4_hex);

		::sscanf(RR1_hex.c_str(), "%lA", &RR(i,0));
		::sscanf(RR2_hex.c_str(), "%lA", &RR(i,1));
		::sscanf(RR3_hex.c_str(), "%lA", &RR(i,2));
		::sscanf(RR4_hex.c_str(), "%lA", &RR(i,3));

		//cout << RR(i,0) << "\t" << RR(i,1) << "\t" << RR(i,2) << "\t" << RR(i,3) << "\n";
	}

	for(int i=0; i<NATOMS; i++){
		infile >> WBX(i,0) >> WBX(i,1) >> WBX(i,2) >> WBX(i,3);
		//cout << WBX(i,0) << "\t" << WBX(i,1) << "\t" << WBX(i,2) << "\t" << WBX(i,3) << "\n";
	}

	for(int i=0; i<NATOMS; i++){
		infile >> WBY(i,0) >> WBY(i,1) >> WBY(i,2) >> WBY(i,3);
		//cout << WBY(i,0) << "\t" << WBY(i,1) << "\t" << WBY(i,2) << "\t" << WBY(i,3) << "\n";
	}

	for(int i=0; i<NATOMS; i++){
		infile >> WBZ(i,0) >> WBZ(i,1) >> WBZ(i,2) >> WBZ(i,3);
		//cout << WBZ(i,0) << "\t" << WBZ(i,1) << "\t" << WBZ(i,2) << "\t" << WBZ(i,3) << "\n";
	}
}

std::vector<ublas::matrix<int>> Simulation::createConTabSO(int i){
	std::vector<ublas::matrix<int>> result;
	ublas::matrix<int> conTabSelf(1,2), conTabOther(1,2);
	int numbCon = 0;
	int line = 0;
	int neighLine = 0;
	for(int j=0; j<4; j++)
		{
			if(GB(i,j)==0)
			{
				numbCon++;
			}
		}
		conTabSelf.resize(numbCon,2);
		conTabOther.resize(numbCon,2);
		for(int j=0;j<4;j++)
		{
			if(GB(i,j)==0)
			{
				conTabSelf(line,0)=i;
				conTabSelf(line,1)=j;
				neighLine=NN(i,j);
				conTabOther(line,0)=neighLine;
				for(int k=0;k<4;k++)
				{
					if(NN(neighLine,k)==i)
					{
						conTabOther(line,1)=k;
					}
				}
				line++;
			}
		}
		result.push_back(conTabSelf);
		result.push_back(conTabOther);
		return result;
}

void Simulation::createCrosslinks(){
	ublas::vector<double> r(3,0);
	std::vector<ublas::matrix<int>> conTabs;
	ublas::matrix<int> conTabSelf, conTabOther;

	for(int i=0;i<NATOMS;i++){
		r[0]=posX[i]; r[1]=posY[i]; r[2]=posZ[i];

		conTabs=createConTabSO(i);

		conTabSelf = conTabs.at(0);
		conTabOther = conTabs.at(1);

		Crosslink cl = Crosslink(i,r,conTabSelf,conTabOther);
		particles_.push_back(cl);
		//cout << cl.getPosition() << "\n";
		//cout << cl.getForce() << "\n";
	}

	/*for(int i=0; i<4;i++){

		cout << particles_.at(i).getConTabSelf() <<  "\t" << particles_.at(i).getConTabOther() << "\n";
	}*/
}

void Simulation::createPairList(){
	int clId1, clId2;
	int nn, gb;
	ublas::vector<int> wb(3,0);
	int filId1,filId2;
	for(int i=0;i<NATOMS;i++)
	{
		for(int j=0;j<4;j++)
		{
			clId1=i;
			nn=NN(i,j);
			gb=GB(i,j);
			if(nn>clId1 && gb==0)
			{
				clId2=nn;
				if(j<2){
					filId1=1;
				}
				else{
					filId1=2;
				}
				// check which filament is cl1 for cl2 on
				for(int k=0;k<4;k++)
				{
					if (NN(nn,k)==i && GB(nn,k)==0) //find cl1 in cl2-line of NN table
					{
						if(k<2){filId2=1;}

						else{filId2=2;}
					}
				}

				CrosslinkPair2 clpair=CrosslinkPair2(particles_.at(clId1), particles_.at(clId2),RR(i,j),WBX(i,j),WBY(i,j),WBZ(i,j), filId1, filId2);
				particlePairs2_.push_back(clpair);
			}
		}
	}
	//WBX(1,1)=5;
	//RR(1,1)=7;
	/*for(int i=0; i<particlePairs2_.size(); i++){
		cout << particlePairs2_.at(i).getCrosslink(1)->getId() << "\t" << particlePairs2_.at(i).getCrosslink(2)->getId() << "\t"
			 << particlePairs2_.at(i).getContourLength() << "\t" << particlePairs2_.at(i).getWBX() << "\t" << particlePairs2_.at(i).getWBY() <<
			 "\t" << particlePairs2_.at(i).getWBZ() << "\t" << particlePairs2_.at(i).getFilId(1) << "\t" << particlePairs2_.at(i).getFilId(2) <<
			 "\t" << particlePairs2_.at(i).getContourLength() << "\n";
	}*/
}

void Simulation::createTripleList(){
	int clId1, clId2, clId3;
	ublas::vector<int> wb1(3,0),wb2(3,0);
	int iDummy; // index of dummy crosslink, 0=no dummy
	int filId1,filId2,filId3;

	for(int i=0; i<NATOMS; i++)
	{
		if(GB(i,0)==0 && GB(i,1)==0){
			clId1=NN(i,0);
			clId2=i;
			clId3=NN(i,1);

			iDummy=0;
		}
		// if cl is the end of a polyer, then one GB=1, that crosslink becomes dummy crosslink
		else if(GB(i,0)==1 && GB(i,1)==0){
			clId1=i; // dummy crosslink
			clId2=i;
			clId3=NN(i,1);

			iDummy=1;
		}
		else if(GB(i,0)==0 && GB(i,1)==1){
			clId1=NN(i,0);
			clId2=i;
			clId3=i;// dummy crosslink

			iDummy=3;
		}

		filId2=1;

		filId1=-1;
		filId3=-1;

		for(int k=0;k<4;k++)
		{	if(iDummy!=1)
			{
				if (NN(clId1,k)==i && GB(clId1,k)==0)
				{
					if(k<2){filId1=1;}

					else{filId1=2;}
				}
			}
			if(iDummy!=3)
			{
				if (NN(clId3,k)==i && GB(clId3,k)==0)
				{
					if(k<2){filId3=1;}

					else{filId3=2;}
				}
			}

		}

		int n1a, n1b, n3a, n3b;

		if (iDummy==0)
		{
			n1a=clId1;
			n3a=clId3;
			for(int k=0;k<4;k++)
			{
				if (NN(clId1,k)==i && GB(clId1,k)==0){ n1b=k;}
				if (NN(clId3,k)==i && GB(clId3,k)==0){ n3b=k;}
			}
		}

		if (iDummy==1)
		{
			n1a=i;
			n1b=0;
			n3a=clId3;
			for(int k=0;k<4;k++)
			{
				if (NN(clId3,k)==i && GB(clId3,k)==0){ n3b=k;}
			}
		}

		if (iDummy==3)
		{
			n3a=i;
			n3b=1;
			n1a=clId1;
			for(int k=0;k<4;k++)
			{
				if (NN(clId1,k)==i && GB(clId1,k)==0){ n1b=k;}
			}
		}

		//CrosslinkTriple2 cltriple=CrosslinkTriple2(particles_.at(clId1), particles_.at(clId2), particles_.at(clId3));
		CrosslinkTriple2 cltriple=CrosslinkTriple2(particles_.at(clId1), particles_.at(clId2), particles_.at(clId3), RR(i,0), RR(i,1), RR(n1a,n1b), RR(n3a,n3b),
				WBX(i,0), WBY(i,0), WBZ(i,0), WBX(i,1), WBY(i,1), WBZ(i,1),iDummy,filId1,filId2,filId3);

		particleTriples2_.push_back(cltriple);

		if(GB(i,2)==0 && GB(i,3)==0){
			clId1=NN(i,2);
			clId2=i;
			clId3=NN(i,3);

			iDummy=0;
		}
		// if cl is the end of a polyer, then one GB=1, that crosslink becomes dummy crosslink
		else if(GB(i,2)==1 && GB(i,3)==0){
			clId1=i; // dummy crosslink
			clId2=i;
			clId3=NN(i,3);

			iDummy=1;
		}
		else if(GB(i,2)==0 && GB(i,3)==1){
			clId1=NN(i,2);
			clId2=i;
			clId3=i;// dummy crosslink

			iDummy=3;
		}
			//CrosslinkTriple2 cltriple=CrosslinkTriple2(particles_.at(clId1), particles_.at(clId2), particles_.at(clId3));

		filId2=2;

		filId1=-1;
		filId3=-1;

		for(int k=0;k<4;k++)
		{	if(iDummy!=1)
			{
				if (NN(clId1,k)==i && GB(clId1,k)==0)
				{
					if(k<2){filId1=1;}

					else{filId1=2;}
				}
			}
			if(iDummy!=3)
			{
				if (NN(clId3,k)==i && GB(clId3,k)==0)
				{
					if(k<2){filId3=1;}

					else{filId3=2;}
				}
			}

		}

		if (iDummy==0)
		{
			n1a=clId1;
			n3a=clId3;
			for(int k=0;k<4;k++)
			{
				if (NN(clId1,k)==i && GB(clId1,k)==0){ n1b=k;}
				if (NN(clId3,k)==i && GB(clId3,k)==0){ n3b=k;}
			}
		}

		if (iDummy==1)
		{
			n1a=i;
			n1b=2;
			n3a=clId3;
			for(int k=0;k<4;k++)
			{
				if (NN(clId3,k)==i && GB(clId3,k)==0){ n3b=k;}
			}
		}

		if (iDummy==3)
		{
			n3a=i;
			n3b=3;
			n1a=clId1;
			for(int k=0;k<4;k++)
			{
				if (NN(clId1,k)==i && GB(clId1,k)==0){ n1b=k;}
			}
		}

		CrosslinkTriple2 cltriple2=CrosslinkTriple2(particles_.at(clId1), particles_.at(clId2), particles_.at(clId3), RR(i,2), RR(i,3), RR(n1a,n1b), RR(n3a,n3b),
			WBX(i,2), WBY(i,2), WBZ(i,2), WBX(i,3), WBY(i,3), WBZ(i,3),iDummy,filId1,filId2,filId3);

		particleTriples2_.push_back(cltriple2);
	}

	/*for(int i=0; i<particleTriples2_.size(); i++){
		cout << particleTriples2_.at(i).getCrosslink(1)->getId() << "\t" << particleTriples2_.at(i).getCrosslink(2)->getId()
		<< "\t" << particleTriples2_.at(i).getCrosslink(3)->getId()<< "\t" << particleTriples2_.at(i).getContourLength(12)
		<< "\t" << particleTriples2_.at(i).getContourLength(23) << "\t" << particleTriples2_.at(i).getWBX(12) << "\t" <<
		particleTriples2_.at(i).getWBY(12) <<  "\t" << particleTriples2_.at(i).getWBZ(12) << "\t" << particleTriples2_.at(i).getWBX(23) << "\t" <<
		particleTriples2_.at(i).getWBY(23) <<  "\t" << particleTriples2_.at(i).getWBZ(23) << "\t" << particleTriples2_.at(i).getDummy() << "\t" <<
		particleTriples2_.at(i).getFilId(1)<<  "\t" << particleTriples2_.at(i).getFilId(2)<< "\t" << particleTriples2_.at(i).getFilId(3) <<
		"\t" << particleTriples2_.at(i).getContourLength(12) << "\t" <<particleTriples2_.at(i).getContourLength(23) <<"\n";
	}*/
}

void Simulation::shear_(){
	FXY=gamma0_*sin(omega_*t_);
}

void Simulation::correctRR_(int opt)
{
	double randNo;
	for(int i=0;i<NATOMS;i++)
	{
		for(int j=0;j<4;j++)
		{
			if(GB(i,j)==1){
				if(opt==1)
				{
					randNo=0.001 + 0.09*((double) rand() / (RAND_MAX));
				}
				else{randNo=0;}
				RR(i,j)=randNo;
			}
		}
	}
}

void Simulation::compareForces_()
{
	ublas::vector<double> Ftot(3,0);
	ublas::vector<double> r_i, r_j;
	double Ftot_ij=0;
	double F=0;
	for(int i=0;i<NATOMS;i++)
	{
		//Ftot += ublas::norm_2(particles_.at(i).getForce());
		Ftot = particles_.at(i).getForce();
		r_i = particles_.at(i).getPosition();
		for(int j=0;j<4;j++)
		{
			if(GB(i,j)==0)
			{
				r_j=particles_.at(NN(i,j)).getPosition();
				Ftot_ij=abs(ublas::inner_prod(Ftot,r_i-r_j));
				F+=Ftot_ij;
			}
		}
	}
	F=F/NATOMS;

	double Fpull=0;
	int nTriples=0;
	for (std::vector<CrosslinkTriple2> ::iterator triplei = particleTriples2_.begin(); triplei != particleTriples2_.end(); triplei++)
	{
		Fpull+=abs(triplei->getfPull());
		nTriples++;
	}
	Fpull=Fpull/nTriples;

	cout << F << "\t" << Fpull << "\n";
}


/*
 * Copyright 2018 University of Leeds
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 * This is being developed for the TANGO Project: http://tango-project.eu
 */

#include <chrono>
#include <fstream>
#include <sstream>
#include <jsoncpp/json/json.h>
#include <NTL/ZZ.h>
#include "inner.h"
#include "InnerMapTask.h"

void run_map_task(int numPerLine, file parametersPath, file inputPath, file resultsPath){
	auto start = std::chrono::high_resolution_clock::now();
	InnerMapTask mapTask(numPerLine, parametersPath, inputPath);
	NTL::ZZ result = mapTask.run();
	auto finish = std::chrono::high_resolution_clock::now();
	auto totalTaskTime = std::chrono::duration_cast<std::chrono::nanoseconds>(finish-start).count();
	Json::Value root;
	std::stringstream ss;
	ss << result;
	root["result"] = ss.str();
	root["task_execution_time"] = Json::UInt64(totalTaskTime);
	root["number_additions"] = Json::UInt64(mapTask.numberAdditions);
	root["total_sum_time"] = Json::UInt64(mapTask.totalSumTime);
	root["number_multiplications"] = Json::UInt64(mapTask.numberMultiplications);
	root["total_product_time"] = Json::UInt64(mapTask.totalProductTime);
	std::ofstream results_ofs(resultsPath);
	if(results_ofs.is_open()){
		results_ofs << root;
		results_ofs.close();
	}
}

void run_map_task_GPU(int numPerLine, file parametersPath, file inputPath, file resultsPath){
	auto start = std::chrono::high_resolution_clock::now();
	InnerMapTask mapTask_GPU(numPerLine, parametersPath, inputPath);
	NTL::ZZ result = mapTask_GPU.run();
	auto finish = std::chrono::high_resolution_clock::now();
	auto totalTaskTime = std::chrono::duration_cast<std::chrono::nanoseconds>(finish-start).count();
	Json::Value root;
	std::stringstream ss;
	ss << result;
	root["result"] = ss.str();
	root["task_execution_time"] = Json::UInt64(totalTaskTime);
	root["number_additions"] = Json::UInt64(mapTask_GPU.numberAdditions);
	root["total_sum_time"] = Json::UInt64(mapTask_GPU.totalSumTime);
	root["number_multiplications"] = Json::UInt64(mapTask_GPU.numberMultiplications);
	root["total_product_time"] = Json::UInt64(mapTask_GPU.totalProductTime);
	std::ofstream results_ofs(resultsPath);
	if(results_ofs.is_open()){
		results_ofs << root;
		results_ofs.close();
	}
}


// Copyright (C) 2017 roki
#ifndef INCLUDED_JPEZY_DECODER_HPP
#define INCLUDED_JPEZY_DECODER_HPP

#include <array>
#include <cassert>
#include <iostream>
#include <srook/string/string_view.hpp>
#include <type_traits>
#include <vector>

#include <boost/range/algorithm/copy.hpp>
#include <boost/range/algorithm/generate.hpp>

#include <srook/algorithm/for_each.hpp>
#include <srook/config/attribute/fallthrough.hpp>
#include <srook/config/feature/if_constexpr.hpp>
#include <srook/config/feature/deduced_typename.hpp>
#include <srook/config/feature/decltype.hpp>
#include <srook/io/bifstream.hpp>
#include <srook/iterator/ostream_joiner.hpp>
#include <srook/iterator/range_access.hpp>
#include <srook/math/constants/algorithm/cos.hpp>
#include <srook/math/constants/algorithm/sqrt.hpp>
#include <srook/mpl/constant_sequence/math/make_costable.hpp>
#include <srook/optional.hpp>
#include <srook/type_traits/is_arithmetic.hpp>
#include <srook/type_traits/remove_reference.hpp>
#include <srook/type_traits/is_same.hpp>

#include "../jpezy.hpp"
#include "tables.hpp"

namespace jpezy {

template <class>
struct decoder;

template <class BuildMode = Release>
struct decoder {
    explicit decoder(const char *filename)
	: pr{
	      jpezy::make_property(
		  (
		      label::_width = 0,
		      label::_height = 0,
		      label::_dimension = 0,
		      label::_sample_precision = 0,
		      label::_comment = "",
		      label::_format = property::Format::undefined,
		      label::_major_rev = srook::byte(0),
		      label::_minor_rev = srook::byte(0),
		      label::_units = property::Units::undefined,
		      label::_width_density = 1,
		      label::_height_density = 1,
		      label::_width_thumbnail = 0,
		      label::_height_thumbnail = 0,
		      label::_extension_code = property::ExtensionCodes::undefined,
		      label::_decodable = property::AnalyzedResult::Yet))},
	  restart_interval{0},
	  rgb{},
	  comp{},
	  pred_dct{},
	  dct{},
	  block{},
	  ht{},
	  qt{},
	  bifs{filename},
	  hmax{},
	  vmax{},
	  enable{false}
    {}

    static SROOK_CONSTEXPR bool is_release_mode = srook::is_same<Release, BuildMode>();

    template <class MODE_TAG = COLOR_MODE>
    srook::optional<srook::array<std::vector<srook::byte>, 3>, srook::optionally::safe_optional_payload> decode()
    {
        raii_messenger mes("process started...");
        std::cout << '\n';

        try {
            analyze_header();
	    } catch (const std::runtime_error &er) {
	        return {};
	    }

        disp_info("\t");
        if (!(pr.get<property::At::Decodable>() & (property::AnalyzedResult::is_htable | property::AnalyzedResult::is_qtable | property::AnalyzedResult::is_start_data))) return {};
        std::unique_ptr<raii_messenger> mes_dec;
        SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>())
            mes_dec = std::make_unique<raii_messenger>("decoding started...", "\t");

        const std::size_t Vblock = get_blocks(pr.get<property::At::VSize>());
        const std::size_t Hblock = get_blocks(pr.get<property::At::HSize>());

        const std::size_t h_unit = (Hblock / hmax) + ((Hblock % hmax) ? 1 : 0);
        const std::size_t v_unit = (Vblock / vmax) + ((Vblock % vmax) ? 1 : 0);
        const std::size_t rgb_s = (v_unit * vmax) * block_size * (h_unit * hmax) * block_size;

        for (auto &&v : rgb) v.resize(rgb_s);

        const std::size_t unit_size = hmax * vmax * blocks_size;
	    comp[0].resize(unit_size);
        for (std::size_t i = 1; i < 3; ++i) comp[i].resize(unit_size, 0x80);

        for (std::size_t uy = 0, restart_counter = 0; uy < v_unit; ++uy) {
            for (std::size_t ux = 0; ux < h_unit; ++ux) {
                try {
                    decode_mcu();
                } catch (const std::runtime_error &er) {
                    std::cerr << "decode_mcu(): throw exception from " << er.what() << std::endl;
                    return {};
                }
                try {
                    make_rgb<MODE_TAG>(ux, uy);
                } catch (const std::runtime_error &er) {
                    std::cerr << "make_rgb(): throw exception from " << er.what() << std::endl;
                    return {};
                }

                try {
                    restart_interval_check(restart_counter);
                } catch (const std::runtime_error &er) {
                    std::cerr << "restart_interval_check(): throw exception from " << er.what() << std::endl;
                } catch (const std::out_of_range &er) {
                    std::cerr << "restart_interval_check(): throw exception from " << er.what() << std::endl;
                }
            }
        }
        for (auto &&v : comp) v.clear();

        return { rgb };
    }

    property pr;

private:
    inline void disp_info(const char *indent = "")
    {
        std::cout << indent << "Loaded JPEG: ";
        std::cout << pr.get<jpezy::property::At::HSize>() << "x" << pr.get<jpezy::property::At::VSize>() << ", ";
        std::cout << "presicion " << pr.get<jpezy::property::At::SamplePrecision>() << ", ";
        std::cout << "\"" << pr.get<jpezy::property::At::Comment>() << "\"" << ", ";
        std::cout << ((pr.get<jpezy::property::At::Format>() == jpezy::property::Format::JFIF) ? "JFIF" : (pr.get<jpezy::property::At::Format>() == jpezy::property::Format::JFXX) ? "JFXX" : "undefined") << " standart ";
        std::cout << srook::to_integer<unsigned int>(pr.get<jpezy::property::At::MajorRevisions>()) << ".0" << srook::to_integer<unsigned int>(pr.get<jpezy::property::At::MinorRevisions>()) << ", ";
        std::cout << ((pr.get<jpezy::property::At::Units>() == jpezy::property::Units::dots_inch) ? "dots inch" : (pr.get<jpezy::property::At::Units>() == jpezy::property::Units::dots_cm) ? "dots cm" : "undefined") << ", ";
        std::cout << "frames " << pr.get<jpezy::property::At::Dimension>() << ", ";
        std::cout << "density " << pr.get<jpezy::property::At::HDensity>() << "x" << pr.get<jpezy::property::At::VDensity>() << "\n" << std::endl;
    }

    void restart_interval_check(std::size_t &restart_counter)
	SROOK_NOEXCEPT(get_marker())
    {
        if (restart_interval) {
            if (++restart_counter >= restart_interval) {
                restart_counter = 0;
                const MARKER mark = get_marker();
                if (mark >= MARKER::RST0 && mark <= MARKER::RST7)
                    pred_dct[0] = pred_dct[1] = pred_dct[2] = 0;
            }
        }
    }

    template <class T, SROOK_REQUIRES(srook::is_arithmetic<T>::value)>
    static SROOK_CONSTEXPR T get_blocks(T x) SROOK_NOEXCEPT_TRUE
    {
        return (x >> 3) + ((x & 0x07) > 0);
    }

    void analyze_header()
    {
        std::unique_ptr<raii_messenger> mes;
        SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>())
            mes = std::make_unique<raii_messenger>("analyzing header...", "\t");

        do {
            if (get_marker() == MARKER::SOI)
                enable = true;
        } while (!enable);

        while (enable) {
            pr.get<property::At::Decodable>() |= analyze_marker();
            if (pr.get<property::At::Decodable>() & property::AnalyzedResult::is_start_data)
                return;
        }
        throw std::runtime_error(__func__);
    }

    void analyze_dht(std::size_t size)
    {
        std::unique_ptr<raii_messenger> mes;
        SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) mes = std::make_unique<raii_messenger>("analyzing DHT...", "\t\t\t");

        const srook::byte *end_add = bifs.next_address();
        if (!end_add) throw std::out_of_range(__func__);
        end_add += size;

        do {
            std::underlying_type_t<srook::byte> uc{};
            (bifs | srook::io::jpeg::bifstream::Byte) >> uc;
            const std::size_t tc = uc >> 4, th = uc & 0x0f;

            if (tc > 1) throw std::runtime_error("DC format error");
            if (th > 3) throw std::runtime_error("AC format error");

            huffman_table &ht_ = ht[tc][th];
            srook::array<std::underlying_type_t<srook::byte>, block_size * 2> cc{};
            std::size_t n_ = 0;

            boost::generate(cc, [this, &n_]() {std::underlying_type_t<srook::byte> b{}; (bifs | srook::io::jpeg::bifstream::Byte) >> b; n_ += b; return b; });
            SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>())
                std::cout << " size: " << n_ << std::endl;
            const std::size_t &n = n_;

            ht_.sizeTP.clear();
            ht_.codeTP.clear();
            ht_.valueTP.clear();
            ht_.sizeTP.resize(n);
            ht_.codeTP.resize(n);
            ht_.valueTP.resize(n);

            for (std::size_t i = 1, k = 0; i <= (block_size * 2); ++i) {
                for (std::size_t j = 1; j <= cc[i - 1]; ++j, ++k) {
                    if (k >= n) throw std::out_of_range("invalid size table");
                    ht_.sizeTP[k] = i;
                }
            }

            for (auto[k, code, si] = std::make_tuple(0u, 0, ht_.sizeTP[0]);;) {
                for (; ht_.sizeTP[k] == si; ++k, ++code)
                    ht_.codeTP[k] = code;

                if (k >= n) break;
                do {
                    code <<= 1;
                    ++si;
                } while (ht_.sizeTP[k] != si);
            }
            for (std::size_t k = 0; k < n; ++k) (bifs | srook::io::jpeg::bifstream::Byte) >> ht_.valueTP[k];

            SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) {
                switch (n) {
                    case 12:
                        std::cout << "found DC Huffman Table... ";
                        break;
                    case 162:
                        std::cout << "found AC Huffman Table... ";
			            break;
                    default:
                        std::cerr << n_ << std::endl;
                        throw std::out_of_range("invalid size table");
                }
            }
        } while (bifs.next_address() < end_add);
    }

    void analyze_dqt(std::size_t size)
    {
        std::unique_ptr<raii_messenger> mes;
        SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) mes = std::make_unique<raii_messenger>("\t\t\tanalyzing DQT...");
        const srook::byte *const end_add = bifs.next_address() + size;
        std::underlying_type_t<srook::byte> c{};

        do {
            (bifs | srook::io::jpeg::bifstream::Byte) >> c;
            if (SROOK_DEDUCED_TYPENAME SROOK_DECLTYPE(qt)::value_type::iterator iter = srook::begin(qt[c & 0x3]); !(c >> 4)) {
                std::underlying_type_t<srook::byte> t{};
                for (std::size_t i = 0; i < blocks_size; ++i) {
                    (bifs | srook::io::jpeg::bifstream::Byte) >> t;
                    iter[ZZ[i]] = int(t);
                }
            } else {
                for (std::size_t i = 0; i < blocks_size; ++i) (bifs | srook::io::jpeg::bifstream::Word) >> iter[ZZ[i]];
            }
        } while (bifs.next_address() < end_add);
    }

    void analyze_frame()
    {
        std::unique_ptr<raii_messenger> mes;
        SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) mes = std::make_unique<raii_messenger>("\t\t\tanalyzing frames...");

        using namespace srook::literals::byte_literals;

        (bifs | srook::io::jpeg::bifstream::Byte) >> pr.get<property::At::SamplePrecision>();
        (bifs | srook::io::jpeg::bifstream::Word) >> pr.get<property::At::VSize>();
        (bifs | srook::io::jpeg::bifstream::Word) >> pr.get<property::At::HSize>();
        (bifs | srook::io::jpeg::bifstream::Byte) >> pr.get<property::At::Dimension>();
        if (pr.get<property::At::Dimension>() != 3 && pr.get<property::At::Dimension>() != 1) throw std::runtime_error("Sorry, this dimension size is not supported");

        SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) std::cout << "VSize: " << pr.get<property::At::VSize>() << " HSize: " << pr.get<property::At::HSize>() << " ";

        for (auto [i, c] = std::make_tuple(0u, 0_byte); i < std::size_t(pr.get<property::At::Dimension>()); ++i) {
            (bifs | srook::io::jpeg::bifstream::Byte) >> fcomp[i].C;
            (bifs | srook::io::jpeg::bifstream::Byte) >> c;

            fcomp[i].H = SROOK_DEDUCED_TYPENAME get_character<srook::byte>::type(c >> 4);

            if (fcomp[i].H > hmax) hmax = fcomp[i].H;
            fcomp[i].V = (std::underlying_type_t<srook::byte>(c) & 0xf);
            if (fcomp[i].V > vmax) vmax = fcomp[i].V;
            (bifs | srook::io::jpeg::bifstream::Byte) >> fcomp[i].Tq;
        }
    }

    void analyze_scan()
    {
        std::unique_ptr<raii_messenger> mes;
        SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) mes = std::make_unique<raii_messenger>("\t\t\tanalyzing scan data...");

        srook::byte c{};
        (bifs | srook::io::jpeg::bifstream::Byte) >> s_header.scan_comp;
        for (std::size_t i = 0; i < srook::to_integer<std::size_t>(s_header.scan_comp); ++i) {
            (bifs | srook::io::jpeg::bifstream::Byte) >> scomp[i].Cs;
            (bifs | srook::io::jpeg::bifstream::Byte) >> c;

            scomp[i].Td = srook::to_integer<std::underlying_type_t<srook::byte>>(c >> 4);
            if (scomp[i].Td > 2) throw std::out_of_range(__func__);

            scomp[i].Ta = (srook::to_integer<std::underlying_type_t<srook::byte>>(c) & 0xf);
            if (scomp[i].Ta > 2) throw std::out_of_range(__func__);

        }

        // unused for DCT
        {
            (bifs | srook::io::jpeg::bifstream::Byte) >> s_header.spectral_begin;
            (bifs | srook::io::jpeg::bifstream::Byte) >> s_header.spectral_end;
            (bifs | srook::io::jpeg::bifstream::Byte) >> c;
            s_header.Ah = srook::to_integer<SROOK_DEDUCED_TYPENAME get_character<srook::byte>::type>(c >> 4);
            s_header.Al = srook::to_integer<std::underlying_type_t<srook::byte>>(c) & 0xf;
        }
    }

    void analyze_jfif()
    {
        std::unique_ptr<raii_messenger> mes;
        SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) mes = std::make_unique<raii_messenger>("\t\t\tanalyzing jfif...");

        pr.get<property::At::Format>() = property::Format::JFIF;
        (bifs | srook::io::jpeg::bifstream::Byte) >> pr.get<property::At::MajorRevisions>();
        (bifs | srook::io::jpeg::bifstream::Byte) >> pr.get<property::At::MinorRevisions>();
        (bifs | srook::io::jpeg::bifstream::Byte) >> pr.get<property::At::Units>();
        (bifs | srook::io::jpeg::bifstream::Word) >> pr.get<property::At::HDensity>();
        (bifs | srook::io::jpeg::bifstream::Word) >> pr.get<property::At::VDensity>();
        (bifs | srook::io::jpeg::bifstream::Byte) >> pr.get<property::At::HThumbnail>();
        (bifs | srook::io::jpeg::bifstream::Byte) >> pr.get<property::At::VThumbnail>();
        pr.get<property::At::Decodable>() |= property::AnalyzedResult::is_jfif;
    }

    void analyze_jfxx()
    {
        std::unique_ptr<raii_messenger> mes;
        SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) mes = std::make_unique<raii_messenger>("\t\t\tanalyzing jfxx...");
        pr.get<property::At::Format>() = property::Format::JFXX;
        (bifs | srook::io::jpeg::bifstream::Byte) >> pr.get<property::At::ExtensionCode>();
    }

    int analyze_marker()
    {
        int length{};
        MARKER mark = get_marker();
        const auto param_offset = [](int &len_) { len_ -= 2; };

        switch (mark) {

            case MARKER::SOF0:
                SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) std::cout << "\t\tfound marker: [SOF0]" << std::endl;
                (bifs | srook::io::jpeg::bifstream::Word) >> length;
                analyze_frame();
                break;
            case MARKER::DHT:
                SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) std::cout << "\t\tfound marker: [DHT]" << std::endl;
                (bifs | srook::io::jpeg::bifstream::Word) >> length;
                param_offset(length);
                analyze_dht(length);
                return property::AnalyzedResult::is_htable;
            case MARKER::DNL:

                SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) std::cout << "\t\tfound marker: [DNL]" << std::endl;
                (bifs | srook::io::jpeg::bifstream::Word) >> length;
                (bifs | srook::io::jpeg::bifstream::Word) >> pr.get<property::At::VSize>();
                break;
            case MARKER::DQT:
                SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) std::cout << "\t\tfound marker: [DQT]" << std::endl;
                (bifs | srook::io::jpeg::bifstream::Word) >> length;
                param_offset(length);
                analyze_dqt(length);
                return property::AnalyzedResult::is_qtable;
            case MARKER::EOI:
                SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) std::cout << "\t\tfound marker: [EOI]" << std::endl;
                enable = false;
                break;
            case MARKER::SOS:
                SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) std::cout << "\t\tfound marker: [SOS]" << std::endl;
                (bifs | srook::io::jpeg::bifstream::Word) >> length;
                analyze_scan();
                return property::AnalyzedResult::is_start_data;
            case MARKER::DRI:
                SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) std::cout << "\t\tfound marker: [DRI]" << std::endl;
                (bifs | srook::io::jpeg::bifstream::Word) >> length;
                (bifs | srook::io::jpeg::bifstream::Word) >> restart_interval;
                break;
            case MARKER::COM:
                SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) std::cout << "\t\tfound marker: [COM]" << std::endl;
                (bifs | srook::io::jpeg::bifstream::Word) >> length;
                param_offset(length);
                (bifs | srook::io::jpeg::bifstream::Byte_n(length)) >> pr.get<property::At::Comment>();
                return property::AnalyzedResult::is_comment;
            // Not supported frames
            case MARKER::SOF1: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::SOF2: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::SOF3: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::SOF5: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::SOF6: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::SOF7: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::SOF9: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::SOF10: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::SOF11: SROOK_ATTRIBUTE_FALLTHROUGH;    case MARKER::SOF13: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::SOF14: SROOK_ATTRIBUTE_FALLTHROUGH;    case MARKER::SOF15: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::EXP: SROOK_ATTRIBUTE_FALLTHROUGH;      case MARKER::DAC: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::DHP:
                std::runtime_error("Not supported");
                break;
            case MARKER::APP0: {
                SROOK_IF_CONSTEXPR (srook::is_same<BuildMode, Debug>()) std::cout << "\n\t\tfound marker: [APP0]" << std::endl;
                (bifs | srook::io::jpeg::bifstream::Word) >> length;
                param_offset(length);

                if (static SROOK_CONSTEXPR char JFIF[] = "JFIF", JFXX[] = "JFXX"; srook::string_view(JFIF).size() == srook::string_view(JFXX).size()) {
                    if (const std::size_t size = srook::string_view(JFIF).size(); length >= signed(size)) {
                        std::string id;
                        id.resize(size + 1);
                        (bifs | srook::io::jpeg::bifstream::Bytes) >> id;
                        id.pop_back();

                        if (id == JFIF) {
                            analyze_jfif();
                            bifs.skip_byte(length - 14);
                        } else if (JFXX == id) {
                            analyze_jfxx();
                            bifs.skip_byte(length - 1);
                        } else {
                            bifs.skip_byte(length - size);
                        }
                    } else {
                        bifs.skip_byte(length);
                    }
                }
                break;
            }

            // Not supported markers
            case MARKER::APP1: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::APP2: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::APP3: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::APP4: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::APP5: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::APP6: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::APP7: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::APP8: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::APP9: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::APP10: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::APP11: SROOK_ATTRIBUTE_FALLTHROUGH;    case MARKER::APP12: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::APP13: SROOK_ATTRIBUTE_FALLTHROUGH;    case MARKER::APP14: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::APP15:
                (bifs | srook::io::jpeg::bifstream::Word) >> length;
                param_offset(length);
                bifs.skip_byte(length);
                break;

	        // reserves and other
            case MARKER::JPG: SROOK_ATTRIBUTE_FALLTHROUGH;      case MARKER::JPG0: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::JPG1: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::JPG2: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::JPG3: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::JPG4: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::JPG5: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::JPG6: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::JPG7: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::JPG8: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::JPG9: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::JPG10: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::JPG11: SROOK_ATTRIBUTE_FALLTHROUGH;    case MARKER::JPG12: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::JPG13: SROOK_ATTRIBUTE_FALLTHROUGH;    case MARKER::TEM: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::Error: SROOK_ATTRIBUTE_FALLTHROUGH;    case MARKER::RST0: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::RST1: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::RST2: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::RST3: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::RST4: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::RST5: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::RST6: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::RST7: SROOK_ATTRIBUTE_FALLTHROUGH;     case MARKER::SOI: SROOK_ATTRIBUTE_FALLTHROUGH;
            case MARKER::RESst: SROOK_ATTRIBUTE_FALLTHROUGH;    case MARKER::RESnd: SROOK_ATTRIBUTE_FALLTHROUGH;

            default:
                throw std::runtime_error("Marker error");
        }
        return property::AnalyzedResult::Yet;
    }

    MARKER get_marker()
    {
        for (std::underlying_type_t<srook::byte> c{};;) {
            (bifs | srook::io::jpeg::bifstream::Byte) >> c;
            if (c == std::underlying_type_t<srook::byte>(MARKER::Marker)) {
                (bifs | srook::io::jpeg::bifstream::Byte) >> c;
                if (c) {
                    if ((c > std::underlying_type_t<srook::byte>(MARKER::RESst)) && (c < std::underlying_type_t<srook::byte>(MARKER::SOF0))) {
                        return MARKER::Error;
                    } else {
                        return MARKER(c);
                    }
                }
            }
        }
        return MARKER::Error;
    }

    void decode_mcu()
    {
        for (std::size_t sc = 0; sc < std::size_t(pr.get<property::At::Dimension>()); ++sc) {
            const std::size_t num_v = fcomp[sc].V;
            const std::size_t num_h = fcomp[sc].H;
            const std::size_t dupc_y = vmax / num_v;
            const std::size_t dupc_x = hmax / num_h;
            const std::size_t v_step = hmax * block_size;

            for (std::size_t ky = 0; ky < num_v; ++ky) {
                for (std::size_t kx = 0; kx < num_h; ++kx) {
                    decode_huffman(sc);
                    inverse_quantization(sc);
                    inverse_dct();

                    SROOK_DEDUCED_TYPENAME srook::remove_reference_t<SROOK_DECLTYPE(comp)>::value_type::iterator tp = std::next(srook::begin(comp[sc]), ky * v_step * block_size + kx * block_size);
                    for (std::size_t y_u = 0; y_u < block_size * dupc_y; ++y_u) {
                        for (std::size_t x_u = 0; x_u < block_size * dupc_x; ++x_u) {
                            tp[y_u * v_step + x_u] = block[(y_u / dupc_y) * block_size + (x_u / dupc_x)];
                        }
                    }
                }
            }
        }
    }

    template <class MODE_TAG>
    void make_rgb(const std::size_t ux, const std::size_t uy) noexcept
    {
        SROOK_DEDUCED_TYPENAME srook::remove_reference_t<SROOK_DECLTYPE(comp)>::value_type::iterator yp = srook::begin(comp[0]), up = srook::begin(comp[1]), vp = srook::begin(comp[2]);
        const std::size_t line = uy * vmax * block_size;
        const std::size_t offset_v = line * pr.get<property::At::HSize>();
        const std::size_t offset_h = ux * hmax * block_size;
        const std::size_t offset = offset_v + offset_h;

        std::vector<srook::byte>::iterator rp = std::next(srook::begin(rgb[0]), offset), gp = std::next(srook::begin(rgb[1]), offset), bp = std::next(srook::begin(rgb[2]), offset);
        const std::size_t end_x = (hmax * block_size);
        const std::size_t end_y = (vmax * block_size);

        for (std::size_t pic_y = 0; pic_y < end_y; ++pic_y) {
            for (std::size_t pic_x = 0; pic_x < end_x; ++pic_x) {
                if (pic_x + offset_h >= pr.get<property::At::HSize>()) {
                    yp += end_x - pic_x;
                    up += end_x - pic_x;
                    vp += end_x - pic_x;
                    break;
                }

                SROOK_IF_CONSTEXPR (const std::size_t index = pic_y * pr.get<property::At::HSize>() + pic_x; srook::is_same<MODE_TAG, COLOR_MODE>()) {
                    rp[index] = revise_value(to_r(*yp, *vp));
                    gp[index] = revise_value(to_g(*yp, *up, *vp));
                    bp[index] = revise_value(to_b(*yp, *up));
                    ++yp;
                    ++vp;
                    ++up;
                } else {
                    gp[index] = bp[index] = rp[index] = revise_value(*yp++);
                }
            }
        }
    }

    SROOK_CONSTEXPR double to_r(const double yp, const double vp) const noexcept
    {
        return yp + (vp - 0x80) * 1.4020;
    }
    SROOK_CONSTEXPR double to_g(const double yp, const double up, const double vp) const noexcept
    {
        return yp - (up - 0x80) * 0.3441 - (vp - 0x80) * 0.7139;
    }
    SROOK_CONSTEXPR double to_b(const double yp, const double up) const noexcept
    {
        return yp + (up - 0x80) * 1.7718;
    }

    struct DC;
    struct AC;

    void decode_huffman(std::size_t sc)
    {
        // DC
        int dc_diff = 0;
        int category = decode_huffman_impl<DC>(sc);
        if (category > 0) {
            (bifs | srook::io::jpeg::bifstream::Bits(category)) >> dc_diff;
            if ((dc_diff & (1 << (category - 1))) == 0) {
                dc_diff -= (1 << category) - 1;
            }
        } else if (category < 0) {
            throw std::runtime_error(__func__);
        }
        pred_dct[sc] += dc_diff;
        dct[0] = pred_dct[sc];

        // AC
        for (std::size_t k = 1; k < blocks_size;) {
            category = decode_huffman_impl<AC>(sc);
            if (!category) {
                for (; k < blocks_size; ++k) dct[ZZ[k]] = 0;
                break;
            } else if (category < 0) {
                throw std::runtime_error(__func__);
            }

            int run = category >> 4, acv = 0;
            category &= 0x0f;
            if (category) {
                (bifs | srook::io::jpeg::bifstream::Bits(category)) >> acv;
                if (!(acv & (1 << (category - 1)))) {
                    acv -= (1 << category) - 1;
                }
            } else if (run != 15) {
                std::runtime_error(__func__);
            }
            if ((run + k) > int(blocks_size - 1)) throw std::runtime_error(__func__);

            for (; run-- > 0; ++k) dct[ZZ[k]] = 0;
            dct[ZZ[k++]] = acv;
        }
    }

    template <class TC>
    int decode_huffman_impl(std::size_t sc)
    {
        SROOK_ST_ASSERT(srook::is_same<TC, DC>() or srook::is_same<TC, AC>());
        huffman_table &ht_ = ht[srook::is_same<TC, AC>()][scomp[sc].Td];
        for (auto[code, length, next, k] = std::make_tuple(0, 0u, 0, 0u); k < ht_.size() && length < (block_size * 2);) {
            ++length;
            code <<= 1;
            (bifs | srook::io::jpeg::bifstream::Bits(1)) >> next;
            if (next < 0) return next;
            code |= next;
            for (; ht_.sizeTP[k] == length; ++k) {
                if (ht_.codeTP[k] == code) return ht_.valueTP[k];
            }
        }
        throw std::runtime_error(__func__);
    }

    void inverse_quantization(std::size_t sc) noexcept
    {
        SROOK_DEDUCED_TYPENAME srook::remove_reference_t<SROOK_DECLTYPE(dct)>::iterator p = srook::begin(dct);
        SROOK_DEDUCED_TYPENAME srook::remove_reference_t<SROOK_DECLTYPE(qt)>::value_type::const_iterator iter = srook::begin(qt[fcomp[sc].Tq]);
        for (std::size_t i = 0; i < blocks_size; ++i) *p++ *= *iter++;
    }

    void inverse_dct() noexcept
    {
        const int sl = pr.get<property::At::SamplePrecision>() == block_size ? 128 : 2048;
        SROOK_CONSTEXPR double disqrt2 = (1.0 / srook::sqrt(2));

        for (std::size_t y = 0; y < block_size; ++y) {
            for (std::size_t x = 0; x < block_size; ++x) {
                double sum = 0;
                for (std::size_t v = 0; v < block_size; ++v) {
                    const double cv = (!v) ? disqrt2 : 1.0;
                    for (std::size_t u = 0; u < block_size; ++u) {
                        const double cu = (!u) ? disqrt2 : 1.0;
                        sum += cu * cv * dct[v * block_size + u] * cos_table[u * block_size + x] * cos_table[v * block_size + y];
                    }
                }
                block[y * block_size + x] = int(sum / 4 + sl);
            }
        }
    }

    SROOK_CONSTEXPR srook::byte revise_value(double v) const noexcept
    {
        using namespace srook::literals::byte_literals;
        return (v < 0.0) ? 0_byte : (v > 255.0) ? 255_byte : srook::byte(v);
    }

public:
    static SROOK_CONSTEXPR std::size_t rgb_size = 3;
    static SROOK_CONSTEXPR std::size_t block_size = 8;
    static SROOK_CONSTEXPR std::size_t blocks_size = block_size * block_size;
    static SROOK_CONSTEXPR std::size_t mcu_size = 4;

private:
    Scan_header s_header;
    srook::array<Frame_component, 3> fcomp;
    srook::array<Scan_component, 3> scomp;

    std::size_t restart_interval;
    srook::array<std::vector<srook::byte>, rgb_size> rgb;
    srook::array<std::vector<int>, rgb_size> comp;
    srook::array<int, rgb_size> pred_dct;
    srook::array<int, blocks_size> dct;
    srook::array<int, blocks_size> block;

    srook::array<srook::array<huffman_table, 4>, 2> ht;
    srook::array<srook::array<int, blocks_size>, mcu_size> qt;
    static SROOK_CONSTEXPR srook::array<const double, block_size * block_size> cos_table = srook::constant_sequence::math::unwrap_costable::array<srook::constant_sequence::math::make_costable_t<8,8>>::value;

    srook::io::jpeg::bifstream bifs;
    std::make_signed_t<std::underlying_type_t<srook::byte>> hmax, vmax;
    bool enable;
};

} // namespace jpezy
#endif


/*
 *   This file is part of SRS project.
 *
 *   SRS is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   SRS is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with SRS. If not, see <http://www.gnu.org/licenses/>.
 *
 *   Created by: Yifang Sun, Jianbin Qin
 *   Last modified by: Yifang Sun, Jianbin Qin
 */

#include <iostream>
#include <fstream>
#include <stdio.h>
#include <getopt.h>
#include <cstdlib>
#include <dirent.h>
#include <vector>
#include <time.h>
#include <sys/time.h>
#include <boost/math/distributions/chi_squared.hpp>
#include "SRSInMemory.h"

#define no_argument 0
#define required_argument 1
#define optional_argument 2

void usage();
bool file_exists(const char *);
bool dir_exists(const char *foldername);
double cal_thres(double c, double p_thres, int m);

template<class T>
void query_workload(SRS_In_Memory<T> * searcher, int k, int t, double thres,
                    char *query_file_path, char *ground_truth_file_path,
                    char *output_file_path);

float diff_timeval(timeval t1, timeval t2) {
  return (float) (t1.tv_sec - t2.tv_sec) + (t1.tv_usec - t2.tv_usec) * 1e-6;
}

int main(int argc, char * argv[]) {
  const struct option longopts[] ={
    {"help",                        no_argument,       0, 'h'},
    {"page-size",                   required_argument, 0, 'b'},
    {"approximation-ratio",         required_argument, 0, 'c'},
    {"dimension",                   required_argument, 0, 'd'},
    {"seed",                        required_argument, 0, 'e'},
    {"ground-truth-file-path",      required_argument, 0, 'g'},
    {"index-dir-path",              required_argument, 0, 'i'},
    {"is-index",                    no_argument,       0, 'I'},
    {"k",                           required_argument, 0, 'k'},
    {"m",                           required_argument, 0, 'm'},
    {"cardinality",                 required_argument, 0, 'n'},
    {"output-file-path",            required_argument, 0, 'o'},
    {"query-file-path",             required_argument, 0, 'q'},
    {"is-query",                    no_argument,       0, 'Q'},
    {"threshold",                   required_argument, 0, 'r'},
    {"dataset-file-path",           required_argument, 0, 's'},
    {"max-number-of-points",        required_argument, 0, 't'},
    {"data-type",                   required_argument, 0, 'y'},
    {0, 0, 0, 0},
  };

  srand(1000);
  int index;
  int iarg = 0;
  opterr = 1;    //getopt error message (off: 0)

  int d = -1;
  long long n = -1;
  int k = -1;
  int b = -1;
  int m = -1;
  int t = -1;

  double c = -1.0;
  double p_thres = -1.0;

  char ground_truth_file_path[100] = "";
  char query_file_path[100] = "";
  char data_file_path[100] = "";
  char output_file_path[100] = "";  //output file, currently not used.
  char index_dir_path[100] = "";
  char data_type[100] = "";

  bool is_valid_command = true;
  bool is_index = false;
  bool is_query = false;
  bool is_integer = true;

  while (iarg != -1) {
    iarg = getopt_long(argc, argv, "b:c:d:e:g:i:k:m:n:o:q:r:s:t:y:hIQ",
                       longopts, &index);

    switch (iarg) {
      case 'b':
        if (optarg) {
          b = atoi(optarg);
        }
        break;
      case 'c':
        if (optarg) {
          c = atof(optarg);
        }
        break;
      case 'd':
        if (optarg) {
          d = atoi(optarg);
        }
        break;
      case 'e':
        if (optarg) {
          srand (atoi(optarg));}
          break;
          case 'g':
          if (optarg) {
            strcpy(ground_truth_file_path, optarg);
          }
          break;
          case 'h':
          usage();
          return 0;
          case 'i':
          if (optarg) {
            strcpy(index_dir_path, optarg);
          }
          break;
          case 'I':
          is_index = true;
          break;
          case 'k':
          if (optarg) {
            k = atoi(optarg);
          }
          break;
          case 'm':
          if (optarg) {
            m = atoi(optarg);
          }
          break;
          case 'n':
          if (optarg) {
            n = atoi(optarg);
          }
          break;
          case 'o':
          if (optarg) {
            strcpy(output_file_path, optarg);
          }
          break;
          case 'q':
          if (optarg) {
            strcpy(query_file_path, optarg);
          }
          break;
          case 'Q':
          is_query = true;
          break;
          case 'r':
          if (optarg) {
            p_thres = atof(optarg);
          }
          break;
          case 's':
          if (optarg) {
            strcpy(data_file_path, optarg);
          }
          break;
          case 't':
          if (optarg) {
            t = atoi(optarg);
          }
          break;
          case 'y':
          if (optarg) {
            if (strcmp(optarg, "f") == 0) {
              is_integer = false;
            } else if (strcmp(optarg, "i") != 0) {
              is_valid_command = false;
            }
          }
          break;

        }
      }

  if (is_index == is_query) {
    is_valid_command = false;
  }
  if (is_index) {
    if (!is_valid_command || d < 0 || m < 0 || n < 0
        || !file_exists(data_file_path) || !dir_exists(index_dir_path)) {
      is_valid_command = false;
    } else if (b < 0) {
      if (is_integer) {
        SRS_In_Memory<int> * indexer = new SRS_In_Memory<int>(index_dir_path);
        indexer->build_index(n, d, m, data_file_path);
        delete indexer;
      } else {
        SRS_In_Memory<float> * indexer = new SRS_In_Memory<float>(
            index_dir_path);
        indexer->build_index(n, d, m, data_file_path);
        delete indexer;
      }
    } else {
      printf("use R-tree here\n");
    }
  }
  if (is_query) {
    if (!is_valid_command || k < 0 || c < 1 || t < 0 || p_thres < 0
        || !file_exists(query_file_path) || !file_exists(ground_truth_file_path)
        || !dir_exists(index_dir_path)) {
      is_valid_command = false;
    } else {
      char * type = new char[10];
      float * temp = readParamFile(index_dir_path, n, d, m, b, type);
      delete[] temp;
      if (b == -1) {
        if (strcmp(type, "int") == 0) {
          SRS_In_Memory<int> * searcher = new SRS_In_Memory<int>(
              index_dir_path);
          searcher->restore_index();
          query_workload(searcher, k, t, cal_thres(c, p_thres, m),
                         query_file_path, ground_truth_file_path,
                         output_file_path);
          delete searcher;
        } else if (strcmp(type, "float") == 0) {
          SRS_In_Memory<float> * searcher = new SRS_In_Memory<float>(
              index_dir_path);
          searcher->restore_index();
          query_workload(searcher, k, t, cal_thres(c, p_thres, m),
                         query_file_path, ground_truth_file_path,
                         output_file_path);
          delete searcher;
        }
        delete[] type;
      } else {
        printf("use R-tree here\n");
      }
    }
  }

  if (!is_valid_command) {
    usage();
  }

  return 0;
}

void usage() {
  printf("SRS-Mem (v1.0)\n");
  printf("Options\n");
  //printf("-b {value}\tpage size in bytes (need to be a multiple of 4)\n");
  printf("-c {value}\tapproximation ratio (>= 1)\n");
  printf("-d {value}\tdimensionality of data\n");
  printf("-e {value}\tseed for random generators\n");
  printf("-g {string}\tground truth file\n");
  printf("-i {string}\tsrs index path (dir)\n");
  printf("-I (function)\tindex data\n");
  printf("-k {value}\tnumber of neighbors wanted\n");
  printf("-m {value}\tdimensionality of the projected space\n");
  printf("-n {value}\tcardinality\n");
  //printf("-o {string}\toutput file\n");
  printf("-q {string}\tquery file\n");
  printf("-Q (function)\tprocess queries\n");
  printf("-r {value}\tthreshold of early termination condition\n");
  printf("-s {string}\tdataset file\n");
  printf("-t {value}\tmaximum number of verify points\n");
  printf(
      "-y {string}\tdata type (i: integer; f: floating number), default value: integer\n");
  printf("\n");
  printf("Usage:\n");

  // printf("Index data (using R-tree)\n");
  // printf("-I -b -d -i -m -n -s\n");

  printf("Index data (using cover-tree)\n");
  printf("-I -d -i -m -n -s [-y]\n");

  printf("Process queries\n");
  printf("-Q -c -g -i -k -q -r -t\n");
}

bool file_exists(const char *filename) {
  std::ifstream ifile(filename);
  if (!ifile) {
    fprintf(stderr, "cannot open file %s\n", filename);
  }
  return bool(ifile);
}

bool dir_exists(const char *dirname) {
  DIR * dir = opendir(dirname);
  if (dir) {
    closedir(dir);
    return true;
  }
  fprintf(stderr, "cannot open dir %s\n", dirname);
  return false;
}

template<class T>
void query_workload(SRS_In_Memory<T> * searcher, int k, int t, double thres,
                    char *query_file_path, char *ground_truth_file_path,
                    char *output_file_path) {
  typedef typename Accumulator<T>::Type ResultType;
  int qn, d, gn, gk, tmp;
  FILE *qfp = fopen(query_file_path, "r");
  FILE *gfp = fopen(ground_truth_file_path, "r");

  fscanf(qfp, "%d %d", &qn, &d);
  fscanf(gfp, "%d %d", &gn, &gk);
  if (gn != qn || gk < k) {
    fprintf(
        stderr,
        "Ground truth file not correct! Please re-generate ground truth with correct parameters.\n");
    return;
  }
  double overall_ratio = 0.0;
  double overall_time = 0.0;

  T * query = new T[d];
  float * gt = new float[gk];
  std::vector<res_pair_raw<ResultType> > res;
  for (int i = 0; i < qn; ++i) {
    fscanf(qfp, "%d", &tmp);
    for (int j = 0; j < d; ++j) {
      fscanf(qfp, type_format<T>::format(), &query[j]);
    }
    fscanf(gfp, "%d", &tmp);
    for (int j = 0; j < gk; ++j) {
      fscanf(gfp, "%f", &gt[j]);
    }

    timeval start;
    gettimeofday(&start, NULL);
    searcher->knn_search(query, k, t + k - 1, thres, res);
    timeval end;
    gettimeofday(&end, NULL);
    overall_time += diff_timeval(end, start);

    double ratio = 0.0;
    std::sort(res.begin(), res.end());
    for (int j = 0; j < k; ++j) {
      ratio += sqrt(res[j].dist) / gt[j];
    }
    // de-comment the following line to print out the average ratio for each query
    // printf("%f\n", ratio / k);
    overall_ratio += ratio;
  }

  //printf("=================\n");
  printf("Overall Ratio: %f\n", overall_ratio / k / qn);
  printf("Average Time (s):  %f\n", overall_time / qn);

  delete[] query;
  delete[] gt;
  fclose(qfp);
  fclose(gfp);
}

double cal_thres(double c, double p_thres, int m) {
  if (p_thres >= 1) {
    return -1;
  }
  boost::math::chi_squared chi(m);
  return boost::math::quantile(chi, p_thres) / c / c;
}