/*
Copyright (c) 2013-2018,  Los Alamos National Security, LLC (LANS)
and the University Corporation for Atmospheric Research (UCAR).

Unless noted otherwise source code is licensed under the BSD license.
Additional copyright and license information can be found in the LICENSE file
distributed with this code, or at http://mpas-dev.github.com/license.html
*/

// ===================================================
//! Includes
// ===================================================
//#include <boost/program_options.hpp>
#include <cstring>
#include <vector>
#include <mpi.h>
#include <list>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <fcntl.h>
#include <limits>
#include <cmath>
#include <map>

#ifndef MPASLI_EXTERNAL_INTERFACE_DISABLE_MANGLING
#define velocity_solver_init_mpi velocity_solver_init_mpi_
#define velocity_solver_finalize velocity_solver_finalize_
#define velocity_solver_init_l1l2 velocity_solver_init_l1l2_
#define velocity_solver_solve_l1l2 velocity_solver_solve_l1l2_
#define velocity_solver_init_fo velocity_solver_init_fo_
#define velocity_solver_solve_fo velocity_solver_solve_fo_
#define velocity_solver_init_stokes velocity_solver_init_stokes_
#define velocity_solver_solve_stokes velocity_solver_solve_stokes_
#define velocity_solver_compute_2d_grid velocity_solver_compute_2d_grid_
#define velocity_solver_set_grid_data velocity_solver_set_grid_data_
#define velocity_solver_extrude_3d_grid velocity_solver_extrude_3d_grid_
#define velocity_solver_export_l1l2_velocity velocity_solver_export_l1l2_velocity_
#define velocity_solver_export_2d_data velocity_solver_export_2d_data_
#define velocity_solver_export_fo_velocity velocity_solver_export_fo_velocity_
#define velocity_solver_estimate_SS_SMB velocity_solver_estimate_ss_smb_
#define interface_init_log interface_init_log_
#define interface_redirect_stdout interface_redirect_stdout_
#define interface_reset_stdout interface_reset_stdout_
#define write_ascii_mesh write_ascii_mesh_
#endif

//#include <lifev/core/algorithm/PreconditionerIfpack.hpp>
//#include <lifev/core/algorithm/PreconditionerML.hpp>

//#include <lifev/ice_sheet/solver/IceProblem.hpp>

struct exchange {
  const int procID;
  const std::vector<int> vec;
  mutable std::vector<int> buffer;
  mutable std::vector<double> doubleBuffer;
  mutable MPI_Request reqID;

  exchange(int _procID, int const* vec_first, int const* vec_last,
      int fieldDim = 1);
};

typedef std::list<exchange> exchangeList_Type;

typedef unsigned int ID;
typedef unsigned int UInt;
const ID NotAnId = std::numeric_limits<int>::max();

// ===================================================
//! Interface function
// ===================================================
extern "C" {

int velocity_solver_init_mpi(int* fComm);

void velocity_solver_finalize();

void velocity_solver_set_parameters(double const* gravity_F, double const* ice_density_F, double const* ocean_density_F,
                        double const* sea_level_F, double const* flowParamA_F,
                        double const* flowLawExponent_F, double const* dynamic_thickness_F,
                        double const* clausius_clapeyron_coeff,
                        int const* li_mask_ValueDynamicIce, int const* li_mask_ValueIce,
                        bool const* use_GLP_F);

void velocity_solver_init_l1l2(double const* levelsRatio);

void velocity_solver_init_fo(double const* levelsRatio);

void velocity_solver_solve_l1l2(double const* lowerSurface_F,
    double const* thickness_F, double const* beta_F, double const* temperature_F,
    double* const dirichletVelocityXValue = 0, double* const dirichletVelocitYValue = 0,
    double* u_normal_F = 0,
    double* xVelocityOnCell = 0, double* yVelocityOnCell = 0);

void velocity_solver_solve_fo(double const* bedTopography_F, double const* lowerSurface_F,
    double const* thickness_F, double const* beta_F, double const* smb_F, double const* temperature_F, double const* stiffnessFactor_F,
    double const* effecPress_F,
    double* const dirichletVelocityXValue = 0, double* const dirichletVelocitYValue = 0,
    double* u_normal_F = 0, double* dissipation_heat_F = 0,
    double* xVelocityOnCell = 0, double* yVelocityOnCell = 0, double const * deltat = 0,
    int *error = 0 );

void velocity_solver_compute_2d_grid(int const* verticesMask_F, int const* _cellsMask_F, int const* dirichletNodesMask_F, int const* floatingEdgeMask_F);

void velocity_solver_set_grid_data(int const* _nCells_F, int const* _nEdges_F,
    int const* _nVertices_F, int const* _nLayers, int const* _nCellsSolve_F,
    int const* _nEdgesSolve_F, int const* _nVerticesSolve_F,
    int const* _maxNEdgesOnCell_F, double const* radius_F,
    int const* _cellsOnEdge_F, int const* _cellsOnVertex_F,
    int const* _verticesOnCell_F, int const* _verticesOnEdge_F,
    int const* _edgesOnCell_F, int const* _nEdgesOnCells_F,
    int const* _indexToCellID_F,
    double const* _xCell_F, double const* _yCell_F, double const* _zCell_F,
    double const* _xVertex_F, double const* _yVertex_F, double const* _zVertex_F,
    double const* _areaTriangle_F,
    int const* sendCellsArray_F, int const* recvCellsArray_F,
    int const* sendEdgesArray_F, int const* recvEdgesArray_F,
    int const* sendVerticesArray_F, int const* recvVerticesArray_F);

void velocity_solver_extrude_3d_grid(double const* levelsRatio_F,
    double const* lowerSurface_F, double const* thickness_F);

void velocity_solver_export_l1l2_velocity();

void velocity_solver_export_fo_velocity();

//void velocity_solver_estimate_SS_SMB (const double* u_normal_F, double* sfcMassBal);

void interface_init_log();

void interface_redirect_stdout(int const* iTimestep);

void interface_reset_stdout();

void write_ascii_mesh(int const* indexToCellID_F,
    double const* bedTopography_F, double const* lowerSurface_F,
    double const* beta_F, double const* temperature_F,
    double const* stiffnessFactor_F,
    double const* effecPress_F,
    double const* thickness_F, double const* thicknessUncertainty_F,
    double const* smb_F, double const* smbUncertainty_F,
    double const* bmb_F, double const* bmbUncertainty_F,
    double const* observedSurfaceVelocityX_F, double const* observedSurfaceVelocityY_F,
    double const* observedVelocityUncertainty_F,
    double const* observedThicknessTendency_F, double const * observedThicknessTendencyUncertainty_F);

} // extern "C"

extern int velocity_solver_init_mpi__(int* fComm);
extern void velocity_solver_finalize__();

#ifdef LIFEV
extern void velocity_solver_init_l1l2__(const std::vector<double>& layersRatio, const std::vector<double>& velocityOnVertices, bool initialize_velocity);

extern void velocity_solver_solve_l1l2__(const std::vector<double>& elevationData,
    const std::vector<double>& thicknessData, const std::vector<double>& betaData,
    const std::vector<double>& temperatureData, const std::vector<int>& indexToVertexID,
    std::vector<double>& velocityOnVertices);

extern void velocity_solver_init_fo__(const std::vector<double>& layersRatio, const std::vector<double>& velocityOnVertices, const std::vector<int>& indexToVertexID, bool initialize_velocity);

extern void velocity_solver_export_l1l2_velocity__(const std::vector<double>& layersRatio, const std::vector<double>& elevationData, const std::vector<double>& regulThk,
    const std::vector<int>& mpasIndexToVertexID, MPI_Comm reducedComm);

#endif

extern void velocity_solver_set_physical_parameters__(double const& gravity, double const& ice_density, double const& ocean_density, double const& sea_level, double const& flowParamA,
                        double const& flowLawExponent, double const& dynamic_thickness, bool const& useGLP, double const& clausiusClapeyronCoeff);

extern void velocity_solver_solve_fo__(int nLayers, int nGlobalVertices,
    int nGlobalTriangles, bool ordering, bool first_time_step,
    const std::vector<int>& indexToVertexID,
    const std::vector<int>& indexToTriangleID, double minBeta,
    const std::vector<double>& regulThk,
    const std::vector<double>& levelsNormalizedThickness,
    const std::vector<double>& elevationData,
    const std::vector<double>& thicknessData,
    const std::vector<double>& betaData,
    const std::vector<double>& bedTopographyData,
    const std::vector<double>& smbData,
    const std::vector<double>& stiffnessFactorData,
    const std::vector<double>& effecPressData,
    const std::vector<double>& temperatureOnTetra,
    std::vector<double>& dissipationHeatOnTetra,
    std::vector<double>& velocityOnVertices,
    int& error,
    const double& deltat = 0.0);

#ifdef LIFEV
extern void velocity_solver_compute_2d_grid__(int nGlobalTriangles,
     int nGlobalVertices, int nGlobalEdges,
     const std::vector<int>& indexToVertexID,
     const std::vector<double>& verticesCoords,
     const std::vector<bool>& isVertexBoundary,
     const std::vector<int>& verticesOnTria,
     const std::vector<bool>& isBoundaryEdge,
     const std::vector<int>& trianglesOnEdge,
     const std::vector<int>& trianglesPositionsOnEdge,
     const std::vector<int>& verticesOnEdge,
     const std::vector<int>& indexToEdgeID,
     const std::vector<int>& indexToTriangleID,
     const std::vector < std::pair<int, int> >& procOnInterfaceEdge);

#else
extern void velocity_solver_compute_2d_grid__(MPI_Comm);
#endif

extern void velocity_solver_export_2d_data__(MPI_Comm reducedComm,
    const std::vector<double>& elevationData,
    const std::vector<double>& thicknessData,
    const std::vector<double>& betaData,
    const std::vector<int>& indexToVertexID);

extern void velocity_solver_extrude_3d_grid__(int nLayers, int nGlobalTriangles,
    int nGlobalVertices, int nGlobalEdges, int Ordering, MPI_Comm reducedComm,
    const std::vector<int>& indexToVertexID,
    const std::vector<int>& mpasIndexToVertexID,
    const std::vector<double>& verticesCoords,
    const std::vector<bool>& isVertexBoundary,
    const std::vector<int>& verticesOnTria,
    const std::vector<bool>& isBoundaryEdge,
    const std::vector<int>& trianglesOnEdge,
    const std::vector<int>& trianglesPositionsOnEdge,
    const std::vector<int>& verticesOnEdge,
    const std::vector<int>& indexToEdgeID,
    const std::vector<int>& indexToTriangleID,
    const std::vector<int>& dirichletNodes,
    const std::vector<int>&floatingEdges);

//extern void velocity_solver_export_l1l2_velocity__();

extern void velocity_solver_export_fo_velocity__(MPI_Comm reducedComm);

#ifdef LIFEV
extern int  velocity_solver_initialize_iceProblem__(bool keep_proc, MPI_Comm reducedComm);
#endif

//extern void velocity_solver_estimate_SS_SMB__ (const double* u_normal_F, double* sfcMassBal);

exchangeList_Type unpackMpiArray(int const* array);

bool isGhostTriangle(int i, double relTol = 1e-1);

double signedTriangleArea(const double* x, const double* y);

double signedTriangleArea(const double* x, const double* y, const double* z);

void createReducedMPI(int nLocalEntities, MPI_Comm& reduced_comm_id);

void import2DFields(std::map<int, int> bdExtensionMap, double const* bedTopography_F, double const* lowerSurface_F, double const* thickness_F,
    double const* beta_F = 0, double const* stiffnessFactor_F = 0, double const* effecPress_F = 0, double const* temperature_F = 0, double const* smb_F = 0, double eps = 0);

void import2DFieldsObservations(std::map<int, int> bdExtensionMap,
            double const * lowerSurface_F,
            double const * thickness_F, double const * thicknessUncertainty_F,
            double const * smbUncertainty_F,
            double const * bmb_F, double const * bmbUncertainty_F,
            double const * observedSurfaceVelocityX_F, double const * observedSurfaceVelocityY_F,
            double const * observedSurfaceVelocityUncertainty_F,
            double const * observedThicknessTendency_F, double const * observedThicknessTendencyUncertainty_F,
            int const * indexToCellID_F);

void write_ascii_mesh_field(std::vector<double> fieldData, std::string filenamebase);

void write_ascii_mesh_field_int(std::vector<int> fieldData, std::string filenamebase);

std::vector<int> extendMaskByOneLayer(int const* verticesMask_F);

void extendMaskByOneLayer(int const* verticesMask_F,
    std::vector<int>& extendedFVerticesMask);

void importP0Temperature();

void exportDissipationHeat(double * dissipationHeat_F);

void get_prism_velocity_on_FEdges(double* uNormal,
    const std::vector<double>& velocityOnCells,
    const std::vector<int>& edgeToFEdge);

int initialize_iceProblem(int nTriangles);

void createReverseCellsExchangeLists(exchangeList_Type& sendListReverse_F,
    exchangeList_Type& receiveListReverse_F,
    const std::vector<int>& fVertexToTriangleID,
    const std::vector<int>& fCellToVertexID);

void createReverseEdgesExchangeLists(exchangeList_Type& sendListReverse_F,
    exchangeList_Type& receiveListReverse_F,
    const std::vector<int>& fVertexToTriangleID,
    const std::vector<int>& fEdgeToEdgeID);

void mapCellsToVertices(const std::vector<double>& velocityOnCells,
    std::vector<double>& velocityOnVertices, int fieldDim, int numLayers,
    int ordering);

void mapVerticesToCells(const std::vector<double>& velocityOnVertices,
    double* velocityOnCells, int fieldDim, int numLayers, int ordering);

void computeLocalOffset(int nLocalEntities, int& localOffset,
    int& nGlobalEntities);

void getProcIds(std::vector<int>& field, int const* recvArray);

void getProcIds(std::vector<int>& field, exchangeList_Type const* recvList);

void allToAll(std::vector<int>& field, int const* sendArray,
    int const* recvArray, int fieldDim = 1);

void allToAll(std::vector<int>& field, exchangeList_Type const* sendList,
    exchangeList_Type const* recvList, int fieldDim = 1);

void allToAll(double* field, exchangeList_Type const* sendList,
    exchangeList_Type const* recvList, int fieldDim = 1);

int prismType(long long int const* prismVertexMpasIds, int& minIndex);
void tetrasFromPrismStructured (long long int const* prismVertexMpasIds, long long int const* prismVertexGIds, long long int tetrasIdsOnPrism[][4]);
void computeMap();

void setBdFacesOnPrism (const std::vector<std::vector<std::vector<int> > >& prismStruct, const std::vector<int>& prismFaceIds, std::vector<int>& tetraPos, std::vector<int>& facePos);
void tetrasFromPrismStructured (int const* prismVertexMpasIds, int const* prismVertexGIds, int tetrasIdsOnPrism[][4]);
void procsSharingVertex(const int vertex, std::vector<int>& procIds);

bool belongToTria(double const* x, double const* t, double bcoords[3], double eps = 1e-3);


#include "hyperquadtree.hpp"
//#include <boost/python.hpp>
#include <algorithm>
#include <iterator>
namespace gd {
//using namespace boost::python;

void py_export_schw_hyperquadtree() {
	//NTree<2, double> quadtree(1.,2.,4.,6.);
	/*class_<DFGrid, boost::noncopyable >("DFGrid", init<int, int, double, double, Galaxy*>())
		.def("output_grid", &DFGrid::output_grid)
		.def("n_dofs", &DFGrid::n_dofs)
		.def("refine_global", &DFGrid::refine_global)
		.def("shape_value", &DFGrid::shape_value)
		.def("print_dof_indices_per_face", &DFGrid::print_dof_indices_per_face)
		.def("print_dof_indices_per_face_on_level", &DFGrid::print_dof_indices_per_face_on_level)
		.def("print_vertices", &DFGrid::print_vertices)
			;*/
}

//template<size_t... Indices>
//tuple<> get_indices(

struct DoSomething
{
    template<typename T>
    void operator()(T& t) const
    {
        //t.do_sth();
		//printf("blaat");
    }
};

const int gs_scale = 300;

// this makes gcc happy
template<size_t N, typename... Tail>
struct action;

/*template<int A, int B>
struct static_min {
	enum { value = A < B : A ? B };
};*/
template<int N>
struct gridder {
	template<class Tree, class P>
	void grid(Tree *tree, ofstream& f, P p1, P p2, int *gridsizes) {
		cout << "Error: gridding not implemented for N = " << N << endl;
	}
};

template<>
struct gridder<2>
{
	template<class Tree, class P>
	void grid(Tree *tree, ofstream& f, P p1, P p2, int *gridsizes) {
		double x1 = get<0>(p1);
		double x2 = get<0>(p2);
		double y1 = get<1>(p1);
		double y2 = get<1>(p2);
		cout << "x range" << x1 << " - " << x2 << endl;
		cout << "y range" << y1 << " - " << y2 << endl;
		f << "[";
		for(int xi = 0; xi < gridsizes[0]; xi++) {
			double x = x1 + xi * (x2-x1) / (gridsizes[0]-1);
			f << "[";
			for(int yi = 0; yi < gridsizes[1]; yi++) {
				double y = y1 + yi * (y2-y1) / (gridsizes[1]-1);
				//cout << "x,y" << x << "\t" << y << "\t" << tree->eval(x, y) << endl;
				//cout << "x,y" << x << "\t" << y << "\t" << tree->eval(x, y) << endl;
				f << tree->eval(x, y) << ",";
			}
			f << "],";
			//cout << endl;
		}
		f << "]";
		f.close();
	}
};
template<>
struct gridder<3>
{
	template<class Tree, class P>
	void grid(Tree *tree, ofstream& f, P p1, P p2, int *gridsizes) {
		double x1 = get<0>(p1);
		double x2 = get<0>(p2);
		double y1 = get<1>(p1);
		double y2 = get<1>(p2);
		double z1 = get<2>(p1);
		double z2 = get<2>(p2);
		cout << "x range" << x1 << " - " << x2 << endl;
		cout << "y range" << y1 << " - " << y2 << endl;
		cout << "z range" << z1 << " - " << z2 << endl;
		f << "[";
		for(int xi = 0; xi < gridsizes[0]; xi++) {
			f << "[";
			double x = x1 + xi * (x2-x1) / (gridsizes[0]-1);
			for(int yi = 0; yi < gridsizes[1]; yi++) {
				f << "[";
				double y = y1 + yi * (y2-y1) / (gridsizes[1]-1);
				for(int zi = 0; zi < gridsizes[2]; zi++) {
					double z = z1 + zi * (z2-z1) / (gridsizes[2]-1);
					//cout << "x,y" << x << "\t" << y << "\t" << tree->eval(x, y) << endl;
					//cout << "x,y" << x << "\t" << y << "\t" << tree->eval(x, y) << endl;
					f << tree->eval(x, y, z) << ",";
				}
				f << "],";
			}
			//cout << endl;
			f << "],";
		}
		f << "]";
		f.close();
	}
};

template<size_t N, typename T, typename... Tail>
struct action<N, T, Tail...>  {
	typedef NTree<N, T, T, Tail...> HTree;
	typedef typename HTree::Point Point;
	typedef action<N-1, Tail...> sub_action_type;
	int dim;
	sub_action_type sub_action;
	HTree* tree;
	Point p1, p2;

	action(int dim) : dim(dim), sub_action(dim), tree(NULL) {}
	void init(int subdivides, string name) {
		//QuadTree::Point p1(0.0,0.0);
		if(N == dim) {
			tree = new HTree(p1, p2);
			//cout << "output: " << name << endl;
			while(subdivides--) tree->rootNode.split();
			//tree->output(name);
			//tree.output_unknown(name);
			//tree->output_ps((name+".ps").c_str(), gs_scale, gs_scale);
		}
		else if(N >= 1) {
			sub_action.init(subdivides, name);
		}
	}
	void optimize(double absfraction=0.2, double relfraction=0.05, int Nabsmin=1, int Nrelmin=0, int maxlevel=8, int max_level_difference=0) {
		if(N == dim) {
			double error = 0;
			cout << "integral is: " << tree->integrate(error) << endl;
			cout << "error    is: " << error << endl;
			tree->optimize(absfraction, relfraction, Nabsmin, Nrelmin, maxlevel, max_level_difference);
		} else {
			sub_action.optimize(absfraction, relfraction, Nabsmin, Nrelmin, maxlevel, max_level_difference);
		}
	}
	void limit_level_difference(int max_level_difference) {
		if(N == dim) {
			//double error = 0;
			tree->limit_level_difference(max_level_difference);
		} else {
			sub_action.limit_level_difference(max_level_difference);
		}
	}
	void read(string inputname) {
		if(N == dim) {
			ifstream f;
			string filename_state = inputname + ".state";
			f.open(filename_state.c_str());
			cout << "reading from: " << filename_state << endl;
			if(!f.is_open())
				throw runtime_error((string("file doesn't exists: ") + filename_state));

			//Point p1, p2;
			f >> p1 >> p2;
			cout << p1 << ", " << p2 << endl;

			vector<bool> splits;
			copy(istream_iterator<bool>(f), istream_iterator<bool>(), back_inserter(splits));
			//copy(splits.begin(), splits.end(), ostream_iterator<bool>(cout, " "));
			cout << "size: " << splits.size() << endl;

			//HTree tree(p1, p2);
			tree = new HTree(p1, p2);
			auto b = splits.begin();
			tree->rootNode.split(&b);
			assert(b == splits.end());
			tree->readpoints(inputname+".known");
			tree->readpoints(inputname+".solved");
			/*tree.output_ps((outputname+".check.ps").c_str(), gs_scale, gs_scale);
			tree.readpoints(inputname+".known");
			tree.readpoints(inputname+".solved");
			double error = 0;
			cout << "integral is: " << tree.integrate(error) << endl;
			cout << "error    is: " << error << endl;
			tree.optimize(0.0, 0.0, 10, 0);
			tree.output(outputname);
			tree.output_ps((outputname+".ps").c_str(), gs_scale, gs_scale);*/

		} else if(N >= 1) {
			sub_action.read(inputname);
		}
	}
	void write(string filename) {
		if(N == dim) {
			tree->output(filename);
			tree->output_ps((filename+".eps").c_str(), gs_scale, gs_scale);
		}
		else if(N >= 1) {
			sub_action.write(filename);
		}
	}
	void grid(string name, int* gridsizes) {
		if(N == dim) {
			ofstream f;
			string filename = name+".grid";
			f.open(filename);
			cout << "output filename: " << filename << endl;
			gridder<N> g;
			g.grid(tree, f, p1, p2, gridsizes);
		}
		else if(N >= 1) {
			sub_action.grid(name, gridsizes);
		}
	}
	void set_point1(T value, int point_dim) {
		if(N == dim) {
			do_tuple(p1, point_dim, [&](T& tuple_ref, bool last) { tuple_ref = value;} );
		}
		else if(N >= 1) {
			sub_action.set_point1(value, point_dim);
		}
	}
	void set_point2(T value, int point_dim) {
		if(N == dim) {
			do_tuple(p2, point_dim, [&](T& tuple_ref, bool last) { tuple_ref = value;} );
		}
		else if(N >= 1) {
			sub_action.set_point2(value, point_dim);
		}
	}

};
template<typename T>
struct action<1, T> {
	action(int dim) {}

	void set_point1(T value, int point_dim) {
	}
	void set_point2(T value, int point_dim) {
	}
	void init(int subdivides, string name) {
	}
	void read(string inputname) {
	}
	void write(string inputname) {
	}
	void optimize(double absfraction=0.2, double relfraction=0.05, int Nabsmin=1, int Nrelmin=0, int maxlevel=8, int max_level_difference=0) {}
	void grid(string name, int* gridsizes) {}
	void limit_level_difference(int max_level_difference) {
	}
};

#include <unistd.h>
#include <getopt.h>

int option_do_init = 0;
int option_do_help = 0;
option options[] = {
	{"init", no_argument, 0, 'i'},
	{"grid", no_argument, 0, 'g'},
	{"initialize", no_argument, 0, 'i'},
	{"optimize", no_argument, 0, 't'},
	{"help", no_argument, 0, 'h'},
	{"dimension", required_argument, 0, 'd'},
	{"limit-level-difference", required_argument, 0, 'l'},
	{"subdivides", required_argument, 0, 's'},
	{"output", required_argument, 0, 'o'},
	{"input", optional_argument, 0, 'n'},
	{0}
};

extern "C" int main(int argc, char* const * argv) {
	//tuple<double, double, double> t(1,2,3);
	//tuple<double, double> t2 = get_indices<1,2>(t);
	//tuple<double, double> t(1,2);
	//std::cout << *boost::fusion::begin(t) << '\n';

	//boost::fusion::for_each(t, DoSomething());
	//return 0;

	/*cout << "test: " << log(0) << endl;
	cout << "test: " << exp(log(0)) << endl;
	cout << "test: " << isinf(log(0)) << endl;
	cout << "test: " << (log(0) < 0) << endl;
	cout << "test: " << save_exp(log(0)) << endl;*/
	int indexptr;
	int c;
	int dim = 0;
	int subdivides = 0;
	int max_level_difference = 0;
	bool init = false;
	bool optimize = false;
	bool do_grid = false;
	//bool do_limit_level_difference = false;
	string outputname;
	string inputname;
	while((c = getopt_long(argc, argv, "gn:o:ihd:s:tl:", &options[0], &indexptr)) != -1) {
		switch(c) {
			case 0:
				printf("long option: %s", options[indexptr].name);
				if(optarg)
					printf("with arg: %s", optarg);
				printf("\n");
				break;
			case 'h':
				printf("usage: %s -d <dim> [options]\n", argv[0]);
				break;
			case 'i':
				printf("initialize\n");
				init = true;
				break;
			case 'g':
				printf("grid\n");
				do_grid = true;
				break;
			case 'd':
				dim = atoi(optarg);
				printf("dimension: %i\n", dim);
				break;
			case 'l':
				max_level_difference = atoi(optarg);
				printf("maximum limit difference: %i\n", max_level_difference);
				//do_limit_level_difference = true;
				break;
			case 'o':
				outputname = string(optarg);
				printf("output: %s\n", outputname.c_str());
				break;
			case 'n':
				inputname = string(optarg);
				printf("input: %s\n", inputname.c_str());
				break;
			case 't':
				optimize = true;
				break;
			case 's':
				if(optarg == NULL) {
					fprintf(stderr, "subdivides requires int argument\n");
					exit(-1);
				}
				subdivides = atoi(optarg);
				printf("subdivide: %d\n", subdivides);
				break;
			default:
				abort();
		}
	}

	typedef action<5, double, double, double, double, double> action_highest_type;
	action_highest_type action_highest(dim);
	if(init) {
		for (int i = optind; i < argc; i++) {
			int index = i - optind; // 0 based index
			if(index >= dim) {
				//printf ("do %s\n", argv[i]);
				double value = atof(argv[i]);
				action_highest.set_point2(value, index-dim);
			} else {
				//printf ("do %s\n", argv[i]);
				double value = atof(argv[i]);
				action_highest.set_point1(value, index);
			}
		}
		/*cout << "p1 " << action_init_highest.p1 << endl;
		cout << "p2 " << action_init_highest.p2 << endl;
		cout << "p1 " << action_init_highest.sub_action.p1 << endl;
		cout << "p2 " << action_init_highest.sub_action.p2 << endl;
		cout << "p1s " << tuple_size<typename action_init_highest_type::Point>::value << endl;*/
		//action_init_highest.subdivide(dim, outputname);
		action_highest.init(subdivides, outputname);
		action_highest.write(outputname);
	}
	if(optimize) {
		if((argc-optind) != 5) {
			fprintf(stderr, "please give optimization parameters\n");
			exit(2);
		}
		double absfraction=atof(argv[optind+0]);
		double relfraction=atof(argv[optind+1]);
		int Nabsmin=atoi(argv[optind+2]);
		int Nrelmin=atoi(argv[optind+3]);
		int maxlevel=atoi(argv[optind+4]);
		cout << "absfraction, relfraction, Nabsmin, Nrelmin" << absfraction << "," <<  relfraction << "," <<  Nabsmin << "," << Nrelmin << endl;
		action_highest.read(inputname);
		action_highest.optimize(absfraction, relfraction, Nabsmin, Nrelmin, maxlevel, max_level_difference);
		/*if(do_limit_level_difference) {
			action_highest.limit_level_difference(max_level_difference);
		}*/
		action_highest.write(outputname);
	}
	if(do_grid) {
		cout << "starting gridding..." << endl;
		action_highest.read(inputname);
		cout << "starting gridding..." << endl;
		if((argc-optind) != dim) {
			fprintf(stderr, "please give gridsize with proper dimension\n");
			exit(2);
		}
		cout << "starting gridding..." << endl;
		int * gridsizes = new int[argc-optind];
		for (int i = optind; i < argc; i++) {
			int index = i - optind; // 0 based index
			gridsizes[index] = atoi(argv[i]);
			printf ("gridsizes[%d] = %d\n", index, gridsizes[index]);
		}
		cout << "starting gridding..." << endl;
		action_highest.grid(inputname, gridsizes);
		delete gridsizes;
		//action_init_highest.do_grid(dim, subdivides, inputname, outputname);
	}

	return 0;

	int count = atoi(argv[1]);
	double absfraction = atof(argv[2]);
	int Nabsmin = atoi(argv[3]);
	double relfraction = atof(argv[4]);
	int Nrelmin = atoi(argv[5]);
	cout << "Nabsmin: " << Nabsmin << endl;
	double error = 0;
	//int optimize = atoi(argv[6]);
	NLinear<1, 2, double> linear(3., 2.);
	NLinear<1, 2, double> linear2(4., 6.);
	NLinear<2, 2, double> bilinear(linear, linear2);
	cout << "integral " << linear.integrate() << ", " << linear.integrate(0., 1., 0., 1.) << endl;
	cout << "integral " << linear2.integrate() << ", " << linear2.integrate(0., 1., 0., 1.)  << endl;
	cout << "integral " << linear2.integrate(0., 1.0) << endl;
	cout << "integral " << bilinear.integrate() << endl;
	cout << "integral " << bilinear.integrate(0., 1., 0., 1.) << endl;
	cout << "integral " << bilinear.integrate(0., 0.5, 0., 0.5) << endl;
	cout << "integral " << bilinear.integrate2(0., 0.5, 0., 1., 0., 0.5, 0., 1.0) << endl;
	cout << "integral " << bilinear.integrate2(0.5, 3., 4., 10., 1., 5., 3., 8.0) << endl;
	//cout << "integral " << bilinear.integrate2(1., 5., 3., 8.0, 0.5, 3., 4., 10.) << endl;
	typedef NTree<2, double, double, double> QuadTree;
	typedef NTree<3, double, double, double, double> OcTree;
	QuadTree::Point p1(0.0,0.0);
	QuadTree::Point p2(14.0, 14.);
	set<QuadTree::Point> s;
	s.insert(p1);
	s.insert(p1);
	s.insert(p2);
	QuadTree quadtree(p1, p2);
	OcTree::Point v1(0.0,0.0,0.0);
	OcTree::Point v2(14.0,14.0,14.0);
	OcTree octree(v1, v2);
	//quadtree.add_point(0., 1.);
	//quadtree.rootNode.printinfo();
	/*quadtree.rootNode.walkrange([](double x1, double x2, double y1, double y2) {
		cout << "quad x:[" << x1 << "," << x2 << "]" << " y:[" << y1 << "," << y2 << "]" << endl;
	});
	quadtree.rootNode.walk([](double x, double y, double value) {
		cout << "point x:[" << x << "]" << " y:[" << y << "] value:"  << value << endl;
	});
	quadtree.rootNode.eval([](double x, double y) {
		return x*y;
	});
	quadtree.rootNode.walk([](double x, double y, double value) {
		cout << "point x:[" << x << "]" << " y:[" << y << "] value:"  << value << endl;
	});*/
	/*for_each(quadtree.points.begin(), quadtree.points.end(), [](QuadTree::Point p) {
		cout << "p x:[" << get<0>(p) << "]" << " y:[" << get<1>(p) << "]" << endl;
	});*/
	cout << "split" << endl;
	fflush(stdout);
	cout << "octree: # vertices: " << octree.points.size() << endl;
	octree.rootNode.split();
	//octree.rootNode.split();
	cout << "octree: # vertices: " << octree.points.size() << endl;
	cout << "quadtree: # vertices: " << quadtree.points.size() << endl;
	quadtree.rootNode.split();
	cout << "quadtree: # vertices: " << quadtree.points.size() << endl;

	//octree.rootNode.split();
	//quadtree.rootNode.split();
	octree.eval([](OcTree::Point p) {
		double x = get<0>(p);
		double y = get<1>(p);
		double z = get<2>(p);
		return exp(-0.5*( pow(x-2,2) + pow(y-2, 2) + pow(z-9,2)) ) * 1./(2*M_PI);
	});

	quadtree.eval([](QuadTree::Point p) {
		double x = get<0>(p);
		double y = get<1>(p);
		return
			exp(-0.5*( pow(x-2,2) + pow(y-2, 2) ) ) * 1./(2*M_PI);// +
			//exp(-0.5*( pow(x-8,2) + pow(y-8, 2) ) ) * 1./(2*M_PI);
	});
	cout << "integrated: " << quadtree.integrate(error) << endl;
	//cout << "integrated(oc): " << octree.integrate() << endl;
	for(int i = 0; i < count; i++) {
		//quadtree.rootNode.split();
		if(optimize) {
			quadtree.optimize(absfraction, relfraction, Nabsmin, Nrelmin);
			octree.optimize(absfraction, relfraction, Nabsmin, Nrelmin);
		}
		else {
			quadtree.rootNode.split();
			octree.rootNode.split();
		}
		quadtree.eval([](QuadTree::Point p) {
			double x = get<0>(p);
			double y = get<1>(p);
			return
				exp(-0.5*( pow(x-2,2) + pow(y-2, 2) ) ) * 1./(2*M_PI);// +
				//exp(-0.5*( pow(x-8,2) + pow(y-8, 2) ) ) * 1./(2*M_PI);
		});
		octree.eval([](OcTree::Point p) {
			double x = get<0>(p);
			double y = get<1>(p);
			double z = get<2>(p);
			return exp(-0.5*( pow(x-2,2) + pow(y-2, 2) + pow(z-9,2)) ) * 1./(2*M_PI);
		});
		error = 0.;
		cout << "# of points: " << quadtree.points.size() << endl;
		cout << "integrated: " << quadtree.integrate(error);
		cout << " error = " << error << endl;
		error = 0.;
		cout << "# of points(oc): " << octree.points.size() << endl;
		cout << "integrated(oc): " << octree.integrate(error);
		cout << " error = " << error << endl;
	}
/*
	quadtree.rootNode.split();
	quadtree.rootNode.left.subleft->split();
	quadtree.rootNode.left.subleft->right.subright->split();
	quadtree.rootNode.left.subleft->right.subright->left.subleft->split();
	quadtree.rootNode.left.subleft->right.subright->left.subleft->right.subright->split();
	quadtree.rootNode.split();*/
/*quadtree.rootNode.walk([](double x, double y, double value) {
		cout << "point x:[" << x << "]" << " y:[" << y << "] value:"  << value << endl;
	});*/
	/*for_each(quadtree.points.begin(), quadtree.points.end(), [](QuadTree::Point p) {
		cout << "p x:[" << get<0>(p) << "]" << " y:[" << get<1>(p) << "]" << endl;
	});*/
	quadtree.output_ps("quadtree.ps", 30., 30., 1.);
	quadtree.output_gpl("quadtree.gpl", 30., 30., 1.);
	octree.output_ps<0,1>("octree_xy.ps", 30., 30., 1.);
	octree.output_ps<0,2>("octree_xz.ps", 30., 30., 1.);
	quadtree.output("quadtree.ntree");
	/*cout << "size: " << quadtree.points.size() << endl;
	quadtree.rootNode.split();
	cout << "size: " << quadtree.points.size() << endl;
	quadtree.rootNode.split();
	cout << "size: " << quadtree.points.size() << endl;*/
	//quadtree.rootNode.left.split()
}

};


/*
 * MIT License
 *
 * Copyright (c) 2015 Alexis LE GOADEC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

/**
 * Définition de l'algorithme décidant la connexité d'un hypergraphe.
 */
#ifndef ALGORITHM_INCLUDE_CONNECTED_HH_
#define ALGORITHM_INCLUDE_CONNECTED_HH_

#include "../model/HypergrapheAbstrait.hh"
#include "../model/AlgorithmeAbstrait.hh"

#include <boost/shared_ptr.hpp>
#include <stack>
#include <vector>

/**
 * Algorithme décidant la connexité d'un hypergraphe.
 */
class Connected : public AlgorithmeAbstrait {

public:

	/**
	 * Constructeur.
	 * @param boost::shared_ptr<HypergrapheAbstrait> Pointeur partagé vers l'hypergraphe.
	 */
	Connected(boost::shared_ptr<HypergrapheAbstrait>&);

	/**
	 * Obtenir la structure de résultats.
	 */
	RStructure getResult() const;

	/**
	 * Destructeur.
	 */
	~Connected();

protected:

	/**
	 * Fonction de lancement de l'algorithme.
	 */
	void runAlgorithme();

	/**
	 * Exploration verticale d'un chemin de la matrice.
	 * @param Vecteur des visités.
	 * @param Pile des "à visiter".
	 * @param Identifiant de la ligne.
	 */
	void exploreVertical(std::vector<unsigned int>&, std::stack<unsigned int>&, unsigned int);

	/**
	 * Exploration horizontale d'un chemin dans la matrice.
	 * @param Vecteur des visités.
	 * @param Pile des "à visiter".
	 * @param Identifiant de la colonne.
	 */
	void exploreHorizontal(std::vector<unsigned int>&, std::stack<unsigned int>&, unsigned int);

	/**
	 * Vérifier si un hyper-vertex a déjà été visité.
	 * @param Vecteur des hyper-vertex visités.
	 * @param Identifiant à vérifier.
	 * @return True si déjà visité, False sinon.
	 */
	bool isVertexVisited(std::vector<unsigned int>&, unsigned int) const;

	/**
	 * Vérifier si une hyper-arête a déjà été visitée.
	 * @param Vecteur des hyper-arêtes visités.
	 * @param Identifiant à vérifier.
	 * @return True si déjà visité, False sinon.
	 */
	bool isEdgeVisited(std::vector<unsigned int>&, unsigned int) const;

/*

	bool isPath(HyperVertex&, HyperVertex&) const;

	bool isVisited(std::vector<HyperEdge>&, const HyperEdge&) const;

*/

protected:

	/**
	 * Pointeur partagé vers l'hypergraphe.
	 */
	boost::shared_ptr<HypergrapheAbstrait>
	_ptrHypergrapheAbstrait;

	/**
	 * Structure de résultat.
	 */
	RStructure _result;

};

#endif /* ALGORITHM_INCLUDE_CONNECTED_HH_ */


// Copyright (c) 2014-2017, The Monero Project
// Copyright (c) 2018-2019, The Scala Project
// Copyright (c) 2017 The Masari Project(next_difficulty_v3)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
//    of conditions and the following disclaimer in the documentation and/or other
//    materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include "include_base_utils.h"
#include <vector>
#include <boost/math/special_functions/round.hpp>
#include <boost/algorithm/clamp.hpp>

#include "int-util.h"
#include "crypto/hash.h"
#include "cryptonote_config.h"
#include "misc_language.h"
#include "difficulty.h"
#include "warnings.h"

#define MAX_AVERAGE_TIMESPAN          (uint64_t) DIFFICULTY_TARGET*6
#define MIN_AVERAGE_TIMESPAN          (uint64_t) DIFFICULTY_TARGET/24

#undef SCALA_DEFAULT_LOG_CATEGORY
#define SCALA_DEFAULT_LOG_CATEGORY "difficulty"

namespace cryptonote {

using std::size_t;
using std::uint64_t;
using std::vector;

#if defined(__x86_64__)
static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) {
    low = mul128(a, b, &high);
}

#else

static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) {
    // __int128 isn't part of the standard, so the previous function wasn't portable. mul128() in Windows is fine,
    // but this portable function should be used elsewhere. Credit for this function goes to latexi95.

    uint64_t aLow = a & 0xFFFFFFFF;
    uint64_t aHigh = a >> 32;
    uint64_t bLow = b & 0xFFFFFFFF;
    uint64_t bHigh = b >> 32;

    uint64_t res = aLow * bLow;
    uint64_t lowRes1 = res & 0xFFFFFFFF;
    uint64_t carry = res >> 32;

    res = aHigh * bLow + carry;
    uint64_t highResHigh1 = res >> 32;
    uint64_t highResLow1 = res & 0xFFFFFFFF;

    res = aLow * bHigh;
    uint64_t lowRes2 = res & 0xFFFFFFFF;
    carry = res >> 32;

    res = aHigh * bHigh + carry;
    uint64_t highResHigh2 = res >> 32;
    uint64_t highResLow2 = res & 0xFFFFFFFF;

    //Addition

    uint64_t r = highResLow1 + lowRes2;
    carry = r >> 32;
    low = (r << 32) | lowRes1;
    r = highResHigh1 + highResLow2 + carry;
    uint64_t d3 = r & 0xFFFFFFFF;
    carry = r >> 32;
    r = highResHigh2 + carry;
    high = d3 | (r << 32);
}

#endif

static inline bool cadd(uint64_t a, uint64_t b) {
    return a + b < a;
}

static inline bool cadc(uint64_t a, uint64_t b, bool c) {
    return a + b < a || (c && a + b == (uint64_t) -1);
}

bool check_hash(const crypto::hash &hash, difficulty_type difficulty) {
    uint64_t low, high, top, cur;
    // First check the highest word, this will most likely fail for a random hash.
    mul(swap64le(((const uint64_t *) &hash)[3]), difficulty, top, high);
    if (high != 0) {
        return false;
    }
    mul(swap64le(((const uint64_t *) &hash)[0]), difficulty, low, cur);
    mul(swap64le(((const uint64_t *) &hash)[1]), difficulty, low, high);
    bool carry = cadd(cur, low);
    cur = high;
    mul(swap64le(((const uint64_t *) &hash)[2]), difficulty, low, high);
    carry = cadc(cur, low, carry);
    carry = cadc(high, top, carry);
    return !carry;
}

difficulty_type next_difficulty(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) {

    if(timestamps.size() > DIFFICULTY_WINDOW)
    {
        timestamps.resize(DIFFICULTY_WINDOW);
        cumulative_difficulties.resize(DIFFICULTY_WINDOW);
    }

    size_t length = timestamps.size();
    assert(length == cumulative_difficulties.size());
    if (length <= 1) {
        return 1;
    }
    static_assert(DIFFICULTY_WINDOW >= 2, "Window is too small");
    assert(length <= DIFFICULTY_WINDOW);
    sort(timestamps.begin(), timestamps.end());
    size_t cut_begin, cut_end;
    static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Cut length is too large");
    if (length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) {
        cut_begin = 0;
        cut_end = length;
    } else {
        cut_begin = (length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) + 1) / 2;
        cut_end = cut_begin + (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT);
    }
    assert(/*cut_begin >= 0 &&*/ cut_begin + 2 <= cut_end && cut_end <= length);
    uint64_t time_span = timestamps[cut_end - 1] - timestamps[cut_begin];
    if (time_span == 0) {
        time_span = 1;
    }
    difficulty_type total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin];
    assert(total_work > 0);
    uint64_t low, high;
    mul(total_work, target_seconds, low, high);
    // blockchain errors "difficulty overhead" if this function returns zero.
    // TODO: consider throwing an exception instead
    if (high != 0 || low + time_span - 1 < low) {
        return 0;
    }
    return (low + time_span - 1) / time_span;
}

//Was good but not as good as v3.
difficulty_type next_difficulty_v2(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) {

    if (timestamps.size() > DIFFICULTY_BLOCKS_COUNT_V2)
    {
        timestamps.resize(DIFFICULTY_BLOCKS_COUNT_V2);
        cumulative_difficulties.resize(DIFFICULTY_BLOCKS_COUNT_V2);
    }

    size_t length = timestamps.size();
    assert(length == cumulative_difficulties.size());
    if (length <= 1) {
        return 1;
    }

    sort(timestamps.begin(), timestamps.end());
    size_t cut_begin, cut_end;
    static_assert(2 * DIFFICULTY_CUT_V2 <= DIFFICULTY_BLOCKS_COUNT_V2 - 2, "Cut length is too large");
    if (length <= DIFFICULTY_BLOCKS_COUNT_V2 - 2 * DIFFICULTY_CUT_V2) {
        cut_begin = 0;
        cut_end = length;
    }
    else {
        cut_begin = (length - (DIFFICULTY_BLOCKS_COUNT_V2 - 2 * DIFFICULTY_CUT_V2) + 1) / 2;
        cut_end = cut_begin + (DIFFICULTY_BLOCKS_COUNT_V2 - 2 * DIFFICULTY_CUT_V2);
    }
    assert(/*cut_begin >= 0 &&*/ cut_begin + 2 <= cut_end && cut_end <= length);
    uint64_t total_timespan = timestamps[cut_end - 1] - timestamps[cut_begin];
    if (total_timespan == 0) {
        total_timespan = 1;
    }

    uint64_t timespan_median = 0;
    if (cut_begin > 0 && length >= cut_begin * 2 + 3) {
        std::vector<std::uint64_t> time_spans;
        for (size_t i = length - cut_begin * 2 - 3; i < length - 1; i++) {
            uint64_t time_span = timestamps[i + 1] - timestamps[i];
            if (time_span == 0) {
                time_span = 1;
            }
            time_spans.push_back(time_span);

            //LOG_PRINT_L3("Timespan " << i << ": " << (time_span / 60) / 60 << ":" << (time_span > 3600 ? (time_span % 3600) / 60 : time_span / 60) << ":" << time_span % 60 << " (" << time_span << ")");
        }
        timespan_median = epee::misc_utils::median(time_spans);
    }

    uint64_t timespan_length = length - cut_begin * 2 - 1;
    //LOG_PRINT_L2("Timespan Median: " << timespan_median << ", Timespan Average: " << total_timespan / timespan_length);

    uint64_t total_timespan_median = timespan_median > 0 ? timespan_median * timespan_length : total_timespan * 7 / 10;
    uint64_t adjusted_total_timespan = (total_timespan * 8 + total_timespan_median * 3) / 10; //  0.8A + 0.3M (the median of a poisson distribution is 70% of the mean, so 0.25A = 0.25/0.7 = 0.285M)
    if (adjusted_total_timespan > MAX_AVERAGE_TIMESPAN * timespan_length) {
        adjusted_total_timespan = MAX_AVERAGE_TIMESPAN * timespan_length;
    }
    if (adjusted_total_timespan < MIN_AVERAGE_TIMESPAN * timespan_length) {
        adjusted_total_timespan = MIN_AVERAGE_TIMESPAN * timespan_length;
    }

    difficulty_type total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin];
    assert(total_work > 0);

    uint64_t low, high;
    mul(total_work, target_seconds, low, high);
    if (high != 0) {
        return 0;
    }

    uint64_t next_diff = (low + adjusted_total_timespan - 1) / adjusted_total_timespan;
    if (next_diff < 1) next_diff = 1;
    //LOG_PRINT_L2("Total timespan: " << total_timespan << ", Adjusted total timespan: " << adjusted_total_timespan << ", Total work: " << total_work << ", Next diff: " << next_diff << ", Hashrate (H/s): " << next_diff / target_seconds);

    return next_diff;
}

difficulty_type next_difficulty_v3(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds, bool v4) {

    if (timestamps.size() > DIFFICULTY_BLOCKS_COUNT_V3)
    {
        timestamps.resize(DIFFICULTY_BLOCKS_COUNT_V3);
        cumulative_difficulties.resize(DIFFICULTY_BLOCKS_COUNT_V3);
    }

    size_t length = timestamps.size();
    assert(length == cumulative_difficulties.size());
    if (length <= 1) {
        return 1;
    }

    uint64_t weighted_timespans = 0;
    uint64_t target;

    if (true) {
        uint64_t previous_max = timestamps[0];
        for (size_t i = 1; i < length; i++) {
            uint64_t timespan;
            uint64_t max_timestamp;

            if (timestamps[i] > previous_max) {
                max_timestamp = timestamps[i];
            } else {
                max_timestamp = previous_max;
            }

            timespan = max_timestamp - previous_max;
            if (timespan == 0) {
                timespan = 1;
            } else if (timespan > 10 * target_seconds) {
                timespan = 10 * target_seconds;
            }

            weighted_timespans += i * timespan;
            previous_max = max_timestamp;
        }
        // adjust = 0.99 for N=60, leaving the + 1 for now as it's not affecting N
        target = 99 * (((length + 1) / 2) * target_seconds) / 100;
    } else {
        for (size_t i = 1; i < length; i++) {
            uint64_t timespan;
            if (timestamps[i - 1] >= timestamps[i]) {
                timespan = 1;
            } else {
                timespan = timestamps[i] - timestamps[i - 1];
            }
            if (timespan > 10 * target_seconds) {
                timespan = 10 * target_seconds;
            }
            weighted_timespans += i * timespan;
        }
        target = ((length + 1) / 2) * target_seconds;
    }

    uint64_t minimum_timespan = target_seconds * length / 2;
    if (weighted_timespans < minimum_timespan) {
        weighted_timespans = minimum_timespan;
    }

    difficulty_type total_work = cumulative_difficulties.back() - cumulative_difficulties.front();
    assert(total_work > 0);

    uint64_t low, high;
    mul(total_work, target, low, high);

    if (high != 0) {
        return 0;
    }
    return low / weighted_timespans;
}

difficulty_type next_difficulty_v4(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) {

    if (timestamps.size() > DIFFICULTY_BLOCKS_COUNT_V4)
    {
        timestamps.resize(DIFFICULTY_BLOCKS_COUNT_V4);
        cumulative_difficulties.resize(DIFFICULTY_BLOCKS_COUNT_V4);
    }

    size_t length_cumul_diff = cumulative_difficulties.size();
    if(length_cumul_diff >= DIFFICULTY_BLOCKS_COUNT_V4 - 1) {
        std::vector<difficulty_type> first_diffs;
        std::vector<difficulty_type> mid_diffs;
        std::vector<difficulty_type> last_diffs;
        for (size_t i = 0; i < (DIFFICULTY_BLOCKS_COUNT_V4-30); i++) {
            first_diffs.push_back(cumulative_difficulties[i]);
        }
        for (size_t i = (DIFFICULTY_BLOCKS_COUNT_V4-30); i < (DIFFICULTY_BLOCKS_COUNT_V4-10); i++) {
            mid_diffs.push_back(cumulative_difficulties[i]);
        }
        for (size_t i = (DIFFICULTY_BLOCKS_COUNT_V4*-10); i < DIFFICULTY_BLOCKS_COUNT_V4; i++) {
            last_diffs.push_back(cumulative_difficulties[i]);
        }
        difficulty_type median_first = epee::misc_utils::median(first_diffs);
        difficulty_type median_mid = epee::misc_utils::median(mid_diffs);
        difficulty_type median_last = epee::misc_utils::median(last_diffs);

        if(median_first > (median_mid*6/5) && median_mid > (median_last*10/9))
        {
            timestamps.resize(25);
            cumulative_difficulties.resize(25);
        }
        else if(median_mid > (median_first*6/5) && median_last > (median_mid*10/9)) {
            timestamps.resize(25);
            cumulative_difficulties.resize(25);
        }

    }

    size_t length = timestamps.size();
    assert(length == cumulative_difficulties.size());
    if (length <= 1) {
        return 1;
    }

    uint64_t weighted_timespans = 0;
    uint64_t target;

    int nbShortTsLastNBlocks = 0;
    bool lastTimeWasShort=false;
    int lastShortTimeInARaw = 0;

    int nbLongTsLastNBlocks = 0;
    //bool lastTimeWasLong=false;

    if (true) {
        uint64_t previous_max = timestamps[0];

        for (size_t i = 1; i < length; i++) {
            uint64_t timespan;
            uint64_t max_timestamp;

            if (timestamps[i] > previous_max) {
                max_timestamp = timestamps[i];
            } else {
                max_timestamp = previous_max;
            }

            timespan = max_timestamp - previous_max;
            if (timespan == 0) {
                timespan = 1;
            } else if (timespan > 11 * target_seconds) {
                timespan = 11 * target_seconds;
            }
            if(i>=(length-7)) {
                if(timespan < 30) {
                    nbShortTsLastNBlocks ++;
                    lastTimeWasShort = true;
                    lastShortTimeInARaw ++;
                } else {
                    lastTimeWasShort = false;
                    lastShortTimeInARaw=0;
                }
                if(timespan >100) {
                    nbLongTsLastNBlocks ++;
                    //lastTimeWasLong = true;
                } else {
                    //lastTimeWasLong = false;
                }
            }

            weighted_timespans += i * timespan;
            previous_max = max_timestamp;
        }
        // adjust faster if many blocks fount too fast

        if(lastTimeWasShort) {
            if(nbShortTsLastNBlocks >= 7) {
                weighted_timespans = weighted_timespans *1/2;
            } else if(nbShortTsLastNBlocks == 6) {
                weighted_timespans = weighted_timespans *3/5;
                if(lastShortTimeInARaw ==6) {
                    weighted_timespans = weighted_timespans *7/8;
                }
            } else if(nbShortTsLastNBlocks == 5) {
                weighted_timespans = weighted_timespans *4/5;
                if(lastShortTimeInARaw ==5) {
                    weighted_timespans = weighted_timespans *7/8;
                }
            } else if(nbShortTsLastNBlocks == 4) {
                weighted_timespans = weighted_timespans *9/10;
                if(lastShortTimeInARaw ==4) {
                    weighted_timespans = weighted_timespans *7/8;
                }
            } else if(nbShortTsLastNBlocks == 3  ) {
                weighted_timespans = weighted_timespans *11/12;
                if(lastShortTimeInARaw ==3) {
                    weighted_timespans = weighted_timespans *7/8;
                }
            }
        }

        // adjust = 0.99 for N=60, leaving the + 1 for now as it's not affecting N
        target = 99 * (((length + 1) / 2) * target_seconds) / 100;
    }

    uint64_t minimum_timespan = target_seconds * length / 2;
    if (weighted_timespans < minimum_timespan) {
        weighted_timespans = minimum_timespan;
    }

    difficulty_type total_work = cumulative_difficulties.back() - cumulative_difficulties.front();
    assert(total_work > 0);

    uint64_t low, high;
    mul(total_work, target, low, high);

    if (high != 0) {

        return 0;
    }
    return (low / weighted_timespans);
}

difficulty_type next_difficulty_v5(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) {

    if (timestamps.size() > DIFFICULTY_BLOCKS_COUNT_V4)
    {
        timestamps.resize(DIFFICULTY_BLOCKS_COUNT_V4);
        cumulative_difficulties.resize(DIFFICULTY_BLOCKS_COUNT_V4);
    }

    size_t timestamp_count = timestamps.size();
    assert(timestamp_count == cumulative_difficulties.size());
    if (timestamp_count <= 1) {
       return 1;
    }

    uint64_t prev_max_timestamp = timestamps[0];
    uint64_t weighted_timespans = 0;
    uint64_t target;

    int short_time_count = 0;
    bool last_was_short_time = false;

    for (size_t i = 1; i < timestamp_count; i++) {
	uint64_t max_timestamp = std::max(prev_max_timestamp, timestamps[i]);
        uint64_t timespan = boost::algorithm::clamp<uint64_t>(max_timestamp - prev_max_timestamp, 1, 11 * target_seconds);
 	last_was_short_time = false;

        if (i >= (timestamp_count - 7)) {
	    if (timespan < 90) {
                short_time_count++;
                last_was_short_time = true;
            }
        }
        weighted_timespans += i * timespan;
 	prev_max_timestamp = max_timestamp;
    }

    // Adjust faster if many blocks found too fast
    if (last_was_short_time) {
	switch (short_time_count) {
	    case 7: weighted_timespans = weighted_timespans * 3 / 5; break;
	    case 6: weighted_timespans = weighted_timespans * 5 / 7; break;
	    case 5: weighted_timespans = weighted_timespans * 4 / 5; break;
	    case 4: weighted_timespans = weighted_timespans * 9 / 10; break;
	    case 3: weighted_timespans = weighted_timespans * 11 / 12; break;
	}
    }

    // adjust = 0.99 for N=60, leaving the + 1 for now as it's not affecting N
    target = 99 * (((timestamp_count + 1) / 2) * target_seconds) / 100;

    uint64_t minimum_timespan = target_seconds * timestamp_count / 2;
    if (weighted_timespans < minimum_timespan) {
        weighted_timespans = minimum_timespan;
    }

    difficulty_type total_work = cumulative_difficulties.back() - cumulative_difficulties.front();
    assert(total_work > 0);

    uint64_t low, high;
    mul(total_work, target, low, high);

    if (high != 0) {
        return 0;
    }
    return (low / weighted_timespans);
}
}


/*!
 * \file bingocpp_pymodule.cc
 *
 * \author Geoffrey F. Bomarito
 * \date
 *
 * This file contains the python bindings of the BingoCpp library.
 *
 * Notices
 * -------
 * Copyright 2018 United States Government as represented by the Administrator of
 * the National Aeronautics and Space Administration. No copyright is claimed in
 * the United States under Title 17, U.S. Code. All Other Rights Reserved.
 *
 *
 * Disclaimers
 * -----------
 * No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF
 * ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED
 * TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR
 * FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE
 * SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN
 * ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS,
 * RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS
 * RESULTING FROM USE OF THE SUBJECT SOFTWARE.  FURTHER, GOVERNMENT AGENCY
 * DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF
 * PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS."

 *
 * Waiver and Indemnity:  RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE
 * UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY
 * PRIOR RECIPIENT.  IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY
 * LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH USE,
 * INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM, RECIPIENT'S USE
 * OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD HARMLESS THE UNITED
 * STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR
 * RECIPIENT, TO THE EXTENT PERMITTED BY LAW.  RECIPIENT'S SOLE REMEDY FOR ANY
 * SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT.
 */

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <pybind11/stl.h>

#include <Eigen/Dense>

#include "BingoCpp/agraph.h"
#include "BingoCpp/backend.h"
#include "BingoCpp/explicit_regression.h"
#include "BingoCpp/implicit_regression.h"
#include "BingoCpp/utils.h"

#include "python/py_equation.h"

namespace py = pybind11;
using namespace bingo;

PYBIND11_MODULE(bingocpp, m) {
  m.doc() = "bingocpp module";  // optional module docstring
  m.def("is_cpp", &backend::IsCpp, "is the backend c++");
  m.def("evaluate", &backend::Evaluate, "evaluate");
  m.def("simplify_and_evaluate", &backend::SimplifyAndEvaluate,
        "evaluate after simplification");
  m.def("evaluate_with_derivative", &backend::EvaluateWithDerivative,
        "evaluate with derivative");
  m.def("simplify_and_evaluate_with_derivative",
        &backend::SimplifyAndEvaluateWithDerivative,
        "evaluate with derivative after simplification");
  m.def("get_utilized_commands",
        &backend::GetUtilizedCommands,
        "get the commands that are utilized in a stack");
  m.def("simplify_stack", &backend::SimplifyStack,
        "simplify stack to only utilized commands");

  py::class_<Equation, PyEquation /* <---trampoline */>(m, "Equation")
    .def(py::init<>())
    .def("evaluate_equation_at",
         &Equation::EvaluateEquationAt)
    .def("evaluate_equation_with_x_gradient_at",
         &Equation::EvaluateEquationWithXGradientAt)
    .def("evaluate_equation_with_local_opt_gradient_at",
          &Equation::EvaluateEquationWithLocalOptGradientAt)
    .def("get_latex_string", &Equation::GetLatexString)
    .def("get_stack_string", &Equation::GetStackString)
    .def("get_console_string", &Equation::GetConsoleString)
    .def("get_complexity", &Equation::GetComplexity);

  py::class_<AGraph, Equation>(m, "AGraph")
    .def(py::init<bool & >(), py::arg("manual_constants") = false)
    .def("is_cpp", &AGraph::IsCpp)
    .def_property("command_array",
                  &AGraph::GetCommandArrayModifiable,
                  &AGraph::SetCommandArray)
    .def_property("fitness",
                  &AGraph::GetFitness,
                  &AGraph::SetFitness)
    .def_property("fit_set",
                  &AGraph::IsFitnessSet,
                  &AGraph::SetFitness)
    .def_property("genetic_age",
                  &AGraph::GetGeneticAge,
                  &AGraph::SetGeneticAge)
    .def_property("constants",
                  &AGraph::GetLocalOptimizationParamsModifiable,
                  &AGraph::SetLocalOptimizationParams)
    .def("notify_command_array_modification",
         &AGraph::NotifyCommandArrayModificiation)
    .def("needs_local_optimization", &AGraph::NeedsLocalOptimization)
    .def("get_utilized_commands", &AGraph::GetUtilizedCommands)
    .def("get_number_local_optimization_params",
        &AGraph::GetNumberLocalOptimizationParams)
    .def("get_local_optimization_params",
        &AGraph::GetLocalOptimizationParamsModifiable)
    .def("set_local_optimization_params", &AGraph::SetLocalOptimizationParams)
    .def("evaluate_equation_at", &AGraph::EvaluateEquationAt)
    .def("evaluate_equation_with_x_gradient_at",
        &AGraph::EvaluateEquationWithXGradientAt)
    .def("evaluate_equation_with_local_opt_gradient_at",
        &AGraph::EvaluateEquationWithLocalOptGradientAt)
    .def("__str__", &AGraph::GetConsoleString)
    .def("get_latex_string", &AGraph::GetLatexString)
    .def("get_console_string", &AGraph::GetConsoleString)
    .def("get_stack_string", &AGraph::GetStackString)
    .def("get_complexity", &AGraph::GetComplexity)
    .def("distance", &AGraph::Distance)
    .def("copy", &AGraph::Copy);

  py::class_<ImplicitTrainingData>(m, "ImplicitTrainingData")
    .def(py::init<Eigen::ArrayXXd &>())
    .def(py::init<Eigen::ArrayXXd &, Eigen::ArrayXXd &>())
    .def_readwrite("x", &ImplicitTrainingData::x)
    .def_readwrite("dx_dt", &ImplicitTrainingData::dx_dt)
    .def("__getitem__",
         (ImplicitTrainingData *(ImplicitTrainingData::*)(int))
         &ImplicitTrainingData::GetItem)
    .def("__getitem__",
         (ImplicitTrainingData *(ImplicitTrainingData::*)(const std::vector<int>&))
         &ImplicitTrainingData::GetItem)
    .def("__len__", &ImplicitTrainingData::Size);

  py::class_<ExplicitTrainingData>(m, "ExplicitTrainingData")
    .def(py::init<Eigen::ArrayXXd &, Eigen::ArrayXXd&>())
    .def_readwrite("x", &ExplicitTrainingData::x)
    .def_readwrite("y", &ExplicitTrainingData::y)
    .def("__getitem__",
         (ExplicitTrainingData *(ExplicitTrainingData::*)(int))
         &ExplicitTrainingData::GetItem)
    .def("__getitem__",
         (ExplicitTrainingData *(ExplicitTrainingData::*)(const std::vector<int>&))
         &ExplicitTrainingData::GetItem)
    .def("__len__", &ExplicitTrainingData::Size);

  py::class_<ExplicitRegression>(m, "ExplicitRegression")
    .def(py::init<ExplicitTrainingData *, std::string &>(),
        py::arg("training_data"),
        py::arg("metric")="mae")
    .def("__call__", &ExplicitRegression::EvaluateIndividualFitness)
    .def("evaluate_fitness_vector", &ExplicitRegression::EvaluateFitnessVector);

  py::class_<ImplicitRegression>(m, "ImplicitRegression")
    .def(py::init<ImplicitTrainingData *, int &, bool &, std::string &>(),
         py::arg("training_data"),
         py::arg("required_params") = -1,
         py::arg("normalize_dot") = false,
         py::arg("metric") = "mae")
    .def("__call__", &ImplicitRegression::EvaluateIndividualFitness)
    .def("evaluate_fitness_vector", &ImplicitRegression::EvaluateFitnessVector);

  m.def("calculate_partials", &CalculatePartials);
  m.def("savitzky_golay", &SavitzkyGolay);
  m.def("gen_fact", &GenFact);
  m.def("gram_poly", &GramPoly);
  m.def("gram_weight", &GramWeight);
}


// Copyright (C) 2011-2012 by the BEM++ Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "bempp/common/config_ahmed.hpp"
#include "bempp/common/config_trilinos.hpp"

#include "identity_operator.hpp"

#include "ahmed_aux.hpp"
#include "assembly_options.hpp"
#include "boundary_operator.hpp"
#include "cluster_construction_helper.hpp"
#include "discrete_dense_boundary_operator.hpp"
#include "discrete_sparse_boundary_operator.hpp"
#include "context.hpp"

#include "../common/types.hpp"
#include "../fiber/basis.hpp"
#include "../fiber/explicit_instantiation.hpp"
#include "../fiber/quadrature_strategy.hpp"
#include "../fiber/local_assembler_for_operators.hpp"
#include "../fiber/opencl_handler.hpp"
#include "../fiber/raw_grid_geometry.hpp"
#include "../fiber/scalar_function_value_functor.hpp"
#include "../fiber/default_collection_of_basis_transformations.hpp"
#include "../grid/entity_iterator.hpp"
#include "../grid/geometry_factory.hpp"
#include "../grid/grid.hpp"
#include "../grid/grid_view.hpp"
#include "../grid/mapper.hpp"
#include "../space/space.hpp"

#include "../common/boost_make_shared_fwd.hpp"
#include <boost/type_traits/is_complex.hpp>

#include <tbb/tick_count.h>

#include <stdexcept>
#include <vector>

#ifdef WITH_TRILINOS
// This is a workaround of the problem of the abs() function being declared
// both in Epetra and in AHMED. It relies of the implementation detail (!) that
// in Epetra the declaration of abs is put between #ifndef __IBMCPP__ ...
// #endif. So it may well break in future versions of Trilinos. The ideal
// solution would be for AHMED to use namespaces.
#ifndef __IBMCPP__
#define __IBMCPP__
#include <Epetra_FECrsMatrix.h>
#include <Epetra_LocalMap.h>
#include <Epetra_SerialComm.h>
#undef __IBMCPP__
#else
#include <Epetra_FECrsMatrix.h>
#include <Epetra_LocalMap.h>
#include <Epetra_SerialComm.h>
#endif
#endif // WITH_TRILINOS

namespace Bempp
{

#ifdef WITH_TRILINOS
// Internal helper functions for Epetra
namespace
{

template <typename ValueType>
int epetraSumIntoGlobalValues(Epetra_FECrsMatrix& matrix,
                              const std::vector<int>& rowIndices,
                              const std::vector<int>& colIndices,
                              const arma::Mat<ValueType>& values);

// Specialisation for double -- no intermediate array is needed
template <>
inline int epetraSumIntoGlobalValues<double>(
        Epetra_FECrsMatrix& matrix,
        const std::vector<int>& rowIndices,
        const std::vector<int>& colIndices,
        const arma::Mat<double>& values)
{
    assert(rowIndices.size() == values.n_rows);
    assert(colIndices.size() == values.n_cols);
    return matrix.SumIntoGlobalValues(rowIndices.size(), &rowIndices[0],
                                      colIndices.size(), &colIndices[0],
                                      values.memptr(),
                                      Epetra_FECrsMatrix::COLUMN_MAJOR);
}

// Specialisation for float
template <>
inline int epetraSumIntoGlobalValues<float>(
        Epetra_FECrsMatrix& matrix,
        const std::vector<int>& rowIndices,
        const std::vector<int>& colIndices,
        const arma::Mat<float>& values)
{
    // Convert data from float into double (expected by Epetra)
    arma::Mat<double> doubleValues(values.n_rows, values.n_cols);
    std::copy(values.begin(), values.end(), doubleValues.begin());
    return epetraSumIntoGlobalValues<double>(
                matrix, rowIndices, colIndices, doubleValues);
}

// Specialisation for std::complex<float>.
// WARNING: at present only the real part is taken into account!
// This is sufficient as long as we provide real-valued basis functions only.
template <>
inline int epetraSumIntoGlobalValues<std::complex<float> >(
        Epetra_FECrsMatrix& matrix,
        const std::vector<int>& rowIndices,
        const std::vector<int>& colIndices,
        const arma::Mat<std::complex<float> >& values)
{
    // Extract the real part of "values" into an array of type double
    arma::Mat<double> doubleValues(values.n_rows, values.n_cols);
    // Check whether arma::real returns a view or a copy (if the latter,
    // this assert will fail)
    for (size_t i = 0; i < values.n_elem; ++i)
        doubleValues[i] = values[i].real();
    return epetraSumIntoGlobalValues<double>(
                matrix, rowIndices, colIndices, doubleValues);
}

// Specialisation for std::complex<double>.
// WARNING: at present only the real part is taken into account!
// This is sufficient as long as we provide real-valued basis functions only.
template <>
inline int epetraSumIntoGlobalValues<std::complex<double> >(
        Epetra_FECrsMatrix& matrix,
        const std::vector<int>& rowIndices,
        const std::vector<int>& colIndices,
        const arma::Mat<std::complex<double> >& values)
{
    // Extract the real part of "values" into an array of type double
    arma::Mat<double> doubleValues(values.n_rows, values.n_cols);
    for (size_t i = 0; i < values.n_elem; ++i)
        doubleValues[i] = values[i].real();
    return epetraSumIntoGlobalValues<double>(
                matrix, rowIndices, colIndices, doubleValues);
}

} // anonymous namespace
#endif

////////////////////////////////////////////////////////////////////////////////
// IdentityOperatorId

template <typename BasisFunctionType, typename ResultType>
IdentityOperatorId<BasisFunctionType, ResultType>::IdentityOperatorId(
        const IdentityOperator<BasisFunctionType, ResultType>& op) :
    m_domain(op.domain().get()), m_range(op.range().get()),
    m_dualToRange(op.dualToRange().get())
{
}

template <typename BasisFunctionType, typename ResultType>
size_t IdentityOperatorId<BasisFunctionType, ResultType>::hash() const
{
    typedef IdentityOperator<BasisFunctionType, ResultType>
            OperatorType;
    size_t result = tbb::tbb_hasher(typeid(OperatorType).name());
    tbb_hash_combine(result, m_domain);
    tbb_hash_combine(result, m_range);
    tbb_hash_combine(result, m_dualToRange);
    return result;
}

template <typename BasisFunctionType, typename ResultType>
bool IdentityOperatorId<BasisFunctionType, ResultType>::isEqual(
        const AbstractBoundaryOperatorId &other) const
{
    // dynamic_cast won't suffice since we want to make sure both objects
    // are of exactly the same type (dynamic_cast would succeed for a subclass)
    if (typeid(other) == typeid(*this))
    {
        const IdentityOperatorId& otherCompatible =
            static_cast<const IdentityOperatorId&>(other);
        return (m_domain == otherCompatible.m_domain &&
                m_range == otherCompatible.m_range &&
                m_dualToRange == otherCompatible.m_dualToRange);
    }
    else
        return false;
}

////////////////////////////////////////////////////////////////////////////////
// IdentityOperator

/** \cond PRIVATE */
template <typename BasisFunctionType, typename ResultType>
struct IdentityOperator<BasisFunctionType, ResultType>::Impl
{
    typedef Fiber::ScalarFunctionValueFunctor<CoordinateType>
    TransformationFunctor;

    Impl() : transformations(TransformationFunctor())
    {}

    Fiber::DefaultCollectionOfBasisTransformations<TransformationFunctor>
    transformations;
};
/** \endcond */

template <typename BasisFunctionType, typename ResultType>
IdentityOperator<BasisFunctionType, ResultType>::IdentityOperator(
        const shared_ptr<const Space<BasisFunctionType> >& domain,
        const shared_ptr<const Space<BasisFunctionType> >& range,
        const shared_ptr<const Space<BasisFunctionType> >& dualToRange,
        const std::string& label,
        int symmetry) :
    Base(domain, range, dualToRange, label,
         (symmetry & AUTO_SYMMETRY ?
              (domain == dualToRange ?
                   (boost::is_complex<BasisFunctionType>() ?
                        HERMITIAN :
                        SYMMETRIC | HERMITIAN) :
                   NO_SYMMETRY) :
              symmetry)),
    m_impl(new Impl),
    m_id(boost::make_shared<IdentityOperatorId<BasisFunctionType, ResultType> >(
             *this))
{
    if (domain->grid() != range->grid() ||
            range->grid() != dualToRange->grid())
        throw std::invalid_argument(
                "IdentityOperator::IdentityOperator(): "
                "all three function spaces must be defined on the same grid.");
}

template <typename BasisFunctionType, typename ResultType>
IdentityOperator<BasisFunctionType, ResultType>::IdentityOperator(
        const IdentityOperator& other) :
    Base(other), m_impl(new Impl(*other.m_impl)),
    m_id(other.m_id)
{
}

template <typename BasisFunctionType, typename ResultType>
IdentityOperator<BasisFunctionType, ResultType>::~IdentityOperator()
{
}

template <typename BasisFunctionType, typename ResultType>
IdentityOperator<BasisFunctionType, ResultType>&
IdentityOperator<BasisFunctionType, ResultType>::
operator=(const IdentityOperator& rhs)
{
    if (this != &rhs) {
        Base::operator=(rhs);
        m_impl.reset(new Impl(*rhs.m_impl));
        m_id = rhs.m_id;
    }
}

template <typename BasisFunctionType, typename ResultType>
shared_ptr<const AbstractBoundaryOperatorId>
IdentityOperator<BasisFunctionType, ResultType>::id() const
{
    return m_id;
}

template <typename BasisFunctionType, typename ResultType>
bool IdentityOperator<BasisFunctionType, ResultType>::isLocal() const
{
    return true;
}

template <typename BasisFunctionType, typename ResultType>
shared_ptr<DiscreteBoundaryOperator<ResultType> >
IdentityOperator<BasisFunctionType, ResultType>::assembleWeakFormImpl(
        const Context<BasisFunctionType, ResultType>& context) const
{
    bool verbose = (context.assemblyOptions().verbosityLevel() >=
                    VerbosityLevel::DEFAULT);
    if (verbose)
        std::cout << "Assembling the weak form of operator '"
                  << this->label() << "'..." << std::endl;

    tbb::tick_count start = tbb::tick_count::now();
    std::auto_ptr<LocalAssembler> assembler =this->makeAssembler(
                *context.quadStrategy(), context.assemblyOptions());
    shared_ptr<DiscreteBoundaryOperator<ResultType> > result =
            assembleWeakFormInternalImpl(*assembler, context.assemblyOptions());
    tbb::tick_count end = tbb::tick_count::now();

    if (verbose)
        std::cout << "Assembly of the weak form of operator '" << this->label()
                  << "' took " << (end - start).seconds() << " s" << std::endl;
    return result;
}

template <typename BasisFunctionType, typename ResultType>
shared_ptr<DiscreteBoundaryOperator<ResultType> >
IdentityOperator<BasisFunctionType, ResultType>::assembleWeakFormInternalImpl(
        LocalAssembler& assembler,
        const AssemblyOptions& options) const
{
#ifdef WITH_TRILINOS
    if (options.isSparseStorageOfMassMatricesEnabled())
        return shared_ptr<DiscreteBoundaryOperator<ResultType> >(
                    assembleWeakFormInSparseMode(assembler, options).release());
#endif
    return shared_ptr<DiscreteBoundaryOperator<ResultType> >(
                    assembleWeakFormInDenseMode(assembler, options).release());
}

template <typename BasisFunctionType, typename ResultType>
std::auto_ptr<DiscreteBoundaryOperator<ResultType> >
IdentityOperator<BasisFunctionType, ResultType>::assembleWeakFormInDenseMode(
        LocalAssembler& assembler,
        const AssemblyOptions& options) const
{
    const Space<BasisFunctionType>& testSpace = *this->dualToRange();
    const Space<BasisFunctionType>& trialSpace = *this->domain();

    // Fill local submatrices
    std::auto_ptr<GridView> view = testSpace.grid()->leafView();
    const size_t elementCount = view->entityCount(0);
    std::vector<int> elementIndices(elementCount);
    for (size_t i = 0; i < elementCount; ++i)
        elementIndices[i] = i;
    std::vector<arma::Mat<ResultType> > localResult;
    assembler.evaluateLocalWeakForms(elementIndices, localResult);

    // Create the operator's matrix
    arma::Mat<ResultType> result(testSpace.globalDofCount(),
                                 trialSpace.globalDofCount());
    result.fill(0.);

    // Retrieve global DOFs corresponding to local DOFs on all elements
    std::vector<std::vector<GlobalDofIndex> > trialGdofs(elementCount);
    std::vector<std::vector<GlobalDofIndex> > testGdofs(elementCount);

    // Gather global DOF lists
    const Mapper& mapper = view->elementMapper();
    std::auto_ptr<EntityIterator<0> > it = view->entityIterator<0>();
    while (!it->finished())
    {
        const Entity<0>& element = it->entity();
        const int elementIndex = mapper.entityIndex(element);
        testSpace.getGlobalDofs(element, testGdofs[elementIndex]);
        trialSpace.getGlobalDofs(element, trialGdofs[elementIndex]);
        it->next();
    }

    // Distribute local matrices into the global matrix
    for (size_t e = 0; e < elementCount; ++e)
        for (size_t trialIndex = 0; trialIndex < trialGdofs[e].size(); ++trialIndex)
            for (size_t testIndex = 0; testIndex < testGdofs[e].size(); ++testIndex)
                result(testGdofs[e][testIndex], trialGdofs[e][trialIndex]) +=
                        localResult[e](testIndex, trialIndex);

    return std::auto_ptr<DiscreteBoundaryOperator<ResultType> >(
                new DiscreteDenseBoundaryOperator<ResultType>(result));
}

template <typename BasisFunctionType, typename ResultType>
std::auto_ptr<DiscreteBoundaryOperator<ResultType> >
IdentityOperator<BasisFunctionType, ResultType>::assembleWeakFormInSparseMode(
        LocalAssembler& assembler,
        const AssemblyOptions& options) const
{
#ifdef WITH_TRILINOS
    if (boost::is_complex<BasisFunctionType>::value)
        throw std::runtime_error(
                "IdentityOperator::assembleWeakFormInSparseMode(): "
                "sparse-mode assembly of identity operators for "
                "complex-valued basis functions is not supported yet");

    const Space<BasisFunctionType>& testSpace = *this->dualToRange();
    const Space<BasisFunctionType>& trialSpace = *this->domain();

    // Fill local submatrices
    std::auto_ptr<GridView> view = testSpace.grid()->leafView();
    const size_t elementCount = view->entityCount(0);
    std::vector<int> elementIndices(elementCount);
    for (size_t i = 0; i < elementCount; ++i)
        elementIndices[i] = i;
    std::vector<arma::Mat<ResultType> > localResult;
    assembler.evaluateLocalWeakForms(elementIndices, localResult);

    // Estimate number of entries in each row

    //    This will be useful when we begin to use MPI
    //    // Get global DOF indices for which this process is responsible
    //    const int testGlobalDofCount = testSpace.globalDofCount();
    //    Epetra_Map rowMap(testGlobalDofCount, 0 /* index-base */, comm);
    //    std::vector<int> myTestGlobalDofs(rowMap.MyGlobalElements(),
    //                                      rowMap.MyGlobalElements() +
    //                                      rowMap.NumMyElements());
    //    const int myTestGlobalDofCount = myTestGlobalDofs.size();

    const int testGlobalDofCount = testSpace.globalDofCount();
    const int trialGlobalDofCount = trialSpace.globalDofCount();
    arma::Col<int> nonzeroEntryCountEstimates(testGlobalDofCount);
    nonzeroEntryCountEstimates.fill(0);

    // Global DOF indices corresponding to local DOFs on elements
    std::vector<std::vector<GlobalDofIndex> > trialGdofs(elementCount);
    std::vector<std::vector<GlobalDofIndex> > testGdofs(elementCount);

    // Fill above lists
    const Mapper& mapper = view->elementMapper();
    std::auto_ptr<EntityIterator<0> > it = view->entityIterator<0>();
    while (!it->finished())
    {
        const Entity<0>& element = it->entity();
        const int elementIndex = mapper.entityIndex(element);
        testSpace.getGlobalDofs(element, testGdofs[elementIndex]);
        trialSpace.getGlobalDofs(element, trialGdofs[elementIndex]);
        it->next();
    }

    // Upper estimate for the number of global trial DOFs coupled to a given
    // global test DOF: sum of the local trial DOF counts for each element that
    // contributes to the global test DOF in question
    for (size_t e = 0; e < elementCount; ++e)
        for (size_t testLdof = 0; testLdof < testGdofs[e].size(); ++testLdof)
            nonzeroEntryCountEstimates(testGdofs[e][testLdof]) +=
                    trialGdofs[e].size();

    Epetra_SerialComm comm; // To be replaced once we begin to use MPI
    Epetra_LocalMap rowMap(testGlobalDofCount, 0 /* index_base */, comm);
    Epetra_LocalMap colMap(trialGlobalDofCount, 0 /* index_base */, comm);
    shared_ptr<Epetra_FECrsMatrix> result = boost::make_shared<Epetra_FECrsMatrix>(
                Copy, rowMap, colMap,
                nonzeroEntryCountEstimates.memptr());

    // TODO: make each process responsible for a subset of elements
    // Find maximum number of local dofs per element
    size_t maxLdofCount = 0;
    for (size_t e = 0; e < elementCount; ++e)
        maxLdofCount = std::max(maxLdofCount,
                                testGdofs[e].size() * trialGdofs[e].size());

    // Initialise sparse matrix with zeros at required positions
    arma::Col<double> zeros(maxLdofCount);
    zeros.fill(0.);
    for (size_t e = 0; e < elementCount; ++e)
        result->InsertGlobalValues(testGdofs[e].size(), &testGdofs[e][0],
                                   trialGdofs[e].size(), &trialGdofs[e][0],
                                   zeros.memptr());
    // Add contributions from individual elements
    for (size_t e = 0; e < elementCount; ++e)
        epetraSumIntoGlobalValues(
                    *result, testGdofs[e], trialGdofs[e], localResult[e]);
    result->GlobalAssemble();

    // If assembly mode is equal to ACA and we have AHMED,
    // construct the block cluster tree. Otherwise leave it uninitialized.
    typedef ClusterConstructionHelper<BasisFunctionType> CCH;
    typedef AhmedDofWrapper<CoordinateType> AhmedDofType;
    typedef ExtendedBemCluster<AhmedDofType> AhmedBemCluster;
    typedef bemblcluster<AhmedDofType, AhmedDofType> AhmedBemBlcluster;

    shared_ptr<AhmedBemBlcluster> blockCluster;
    shared_ptr<IndexPermutation> test_o2pPermutation, test_p2oPermutation;
    shared_ptr<IndexPermutation> trial_o2pPermutation, trial_p2oPermutation;
#ifdef WITH_AHMED
    if (options.assemblyMode() == AssemblyOptions::ACA) {
        const AcaOptions& acaOptions = options.acaOptions();
        bool indexWithGlobalDofs = acaOptions.globalAssemblyBeforeCompression;

        typedef ClusterConstructionHelper<BasisFunctionType> CCH;
        shared_ptr<AhmedBemCluster> testClusterTree;
        CCH::constructBemCluster(testSpace, indexWithGlobalDofs, acaOptions,
                                 testClusterTree,
                                 test_o2pPermutation, test_p2oPermutation);
        // TODO: construct a hermitian H-matrix if possible
        shared_ptr<AhmedBemCluster> trialClusterTree;
        CCH::constructBemCluster(trialSpace, indexWithGlobalDofs, acaOptions,
                                 trialClusterTree,
                                 trial_o2pPermutation, trial_p2oPermutation);
        unsigned int blockCount = 0;
        blockCluster.reset(CCH::constructBemBlockCluster(
                               acaOptions, false /* hermitian */,
                               *testClusterTree, *trialClusterTree, blockCount)
                           .release());
    }
#endif

    // Create and return a discrete operator represented by the matrix that
    // has just been calculated
    return std::auto_ptr<DiscreteBoundaryOperator<ResultType> >(
        new DiscreteSparseBoundaryOperator<ResultType>(
                    result, this->symmetry(), NO_TRANSPOSE,
                    blockCluster, trial_o2pPermutation, test_o2pPermutation));
#else // WITH_TRILINOS
    throw std::runtime_error("IdentityOperator::assembleWeakFormInSparseMode(): "
                             "To enable assembly in sparse mode, recompile BEM++ "
                             "with the symbol WITH_TRILINOS defined.");
#endif
}

template <typename BasisFunctionType, typename ResultType>
std::auto_ptr<typename IdentityOperator<BasisFunctionType, ResultType>::LocalAssembler>
IdentityOperator<BasisFunctionType, ResultType>::makeAssemblerImpl(
        const QuadratureStrategy& quadStrategy,
        const shared_ptr<const GeometryFactory>& testGeometryFactory,
        const shared_ptr<const GeometryFactory>& trialGeometryFactory,
        const shared_ptr<const Fiber::RawGridGeometry<CoordinateType> >& testRawGeometry,
        const shared_ptr<const Fiber::RawGridGeometry<CoordinateType> >& trialRawGeometry,
        const shared_ptr<const std::vector<const Fiber::Basis<BasisFunctionType>*> >& testBases,
        const shared_ptr<const std::vector<const Fiber::Basis<BasisFunctionType>*> >& trialBases,
        const shared_ptr<const Fiber::OpenClHandler>& openClHandler,
        const ParallelizationOptions&,
        VerbosityLevel::Level /* verbosityLevel*/,
        bool /* cacheSingularIntegrals */) const
{
    shared_ptr<const Fiber::CollectionOfBasisTransformations<CoordinateType> >
            transformations = make_shared_from_ref(m_impl->transformations);

    if (testGeometryFactory.get() != trialGeometryFactory.get() ||
            testRawGeometry.get() != trialRawGeometry.get())
        throw std::invalid_argument("IdentityOperator::makeAssemblerImpl(): "
                                    "the test and trial spaces must be defined "
                                    "on the same grid");
    return quadStrategy.makeAssemblerForIdentityOperators(
                testGeometryFactory, testRawGeometry,
                testBases, trialBases,
                transformations, transformations,
                openClHandler);
}

template <typename BasisFunctionType, typename ResultType>
BoundaryOperator<BasisFunctionType, ResultType>
identityOperator(const shared_ptr<const Context<BasisFunctionType, ResultType> >& context,
                 const shared_ptr<const Space<BasisFunctionType> >& domain,
                 const shared_ptr<const Space<BasisFunctionType> >& range,
                 const shared_ptr<const Space<BasisFunctionType> >& dualToRange,
                 const std::string& label,
                 int symmetry)
{
    typedef IdentityOperator<BasisFunctionType, ResultType> Id;
    return BoundaryOperator<BasisFunctionType, ResultType>(
                context,
                boost::make_shared<Id>(domain, range, dualToRange,
                                       label, symmetry));
}

#define INSTANTIATE_NONMEMBER_CONSTRUCTOR(BASIS, RESULT) \
    template BoundaryOperator<BASIS, RESULT> \
    identityOperator( \
        const shared_ptr<const Context<BASIS, RESULT> >&, \
        const shared_ptr<const Space<BASIS> >&, \
        const shared_ptr<const Space<BASIS> >&, \
        const shared_ptr<const Space<BASIS> >&, \
        const std::string&, \
        int)
FIBER_ITERATE_OVER_BASIS_AND_RESULT_TYPES(INSTANTIATE_NONMEMBER_CONSTRUCTOR);

FIBER_INSTANTIATE_CLASS_TEMPLATED_ON_BASIS_AND_RESULT(IdentityOperator);

} // namespace Bempp