// 2007 © Václav Šmilauer <eudoxos@arcig.cz>

#pragma once

#include<boost/lambda/lambda.hpp>

#include "lib/base/Math.hpp"
#include "lib/base/Logging.hpp"
#include "core/Body.hpp"

#include<boost/function.hpp>
#include<lib/base/AliasNamespaces.hpp>

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

class Scene;
class Body;
class SimpleViscoelasticBodyParameters;
class ViscElMat;
class FrictMat;
class Interaction;

/*! Miscillaneous utility functions which are believed to be generally useful.
 *
 * All data members are methods are static, no instance of Shop is created. It is not serializable either.
 */

class Shop{
	public:
		DECLARE_LOGGER;

		//! create default sphere, along with its bound etc. 
		static shared_ptr<Body> sphere(Vector3r center, Real radius, shared_ptr<Material> mat);
		//! create default box with everything needed
		static shared_ptr<Body> box(Vector3r center, Vector3r extents, shared_ptr<Material> mat);
		//! create default tetrahedron
		static shared_ptr<Body> tetra(Vector3r v[4], shared_ptr<Material> mat);

		//! return instance of default FrictMat
		static shared_ptr<FrictMat> defaultGranularMat();

		//! Return vector of pairs (center,radius) loaded from a file with numbers inside
		static vector<boost::tuple<Vector3r,Real,int> > loadSpheresFromFile(const string& fname,Vector3r& minXYZ, Vector3r& maxXYZ, Vector3r* cellSize=NULL);
		
		//! Save spheres in the current simulation into a text file
		static void saveSpheresToFile(string fileName);

		//! Compute the total volume of spheres
		static Real getSpheresVolume(const shared_ptr<Scene>& rb=shared_ptr<Scene>(), int mask=-1);
		
		//! Compute the total mass of spheres
		static Real getSpheresMass(const shared_ptr<Scene>& rb=shared_ptr<Scene>(), int mask=-1);
		
		//! Compute porosity; volume must be given for aperiodic simulations
		static Real getPorosity(const shared_ptr<Scene>& rb=shared_ptr<Scene>(),Real volume=-1);

		static Real getPorosityAlt();

		//! Compute porosity by dividing given volume into a grid of voxels;
		static Real getVoxelPorosity(const shared_ptr<Scene>& rb=shared_ptr<Scene>(),int resolution=500,Vector3r start=Vector3r(0,0,0),Vector3r end=Vector3r(0,0,0));

		//! Estimate timestep based on P-wave propagation speed
		static Real PWaveTimeStep(const shared_ptr<Scene> rb=shared_ptr<Scene>());
		
		//! Estimate timestep based on Rayleigh-wave propagation speed
		static Real RayleighWaveTimeStep(const shared_ptr<Scene> rb=shared_ptr<Scene>());

		//! return 2d coordinates of a 3d point within plane defined by rotation axis and inclination of spiral, wrapped to the 0th period
		static boost::tuple<Real, Real, Real> spiralProject(const Vector3r& pt, Real dH_dTheta, int axis=2, Real periodStart=std::numeric_limits<Real>::quiet_NaN(), Real theta0=0);

		//! Calculate inscribed circle center of trianlge
		static Vector3r inscribedCircleCenter(const Vector3r& v0, const Vector3r& v1, const Vector3r& v2);

		/// Get viscoelastic parameters kn,cn,ks,cs from analytical solution of
		/// a problem of interaction of pair spheres with mass 1, collision
		/// time tc and restitution coefficients en,es.
	    static void getViscoelasticFromSpheresInteraction(Real tc, Real en, Real es, shared_ptr<ViscElMat> b);

		//! Get unbalanced force of the whole simulation
		static Real unbalancedForce(bool useMaxForce=false, Scene* _rb=NULL);
		static Real kineticEnergy(Scene* _rb=NULL, Body::id_t* maxId=NULL);
		//! get total momentum of current simulation
		static Vector3r momentum();
		//! get total angular momentum of current simulation
		static Vector3r angularMomentum(Vector3r origin = Vector3r::Zero());

		static Vector3r totalForceInVolume(Real& avgIsoStiffness, Scene *_rb=NULL);


		//! create transientInteraction between 2 bodies, using existing Dispatcher in Omega
		static shared_ptr<Interaction> createExplicitInteraction(Body::id_t id1, Body::id_t id2, bool force, bool virtualI);

		//! apply force on contact point on both bodies (reversed on body 2)
		static void applyForceAtContactPoint(const Vector3r& force, const Vector3r& contPt, Body::id_t id1, const Vector3r& pos1, Body::id_t id2, const Vector3r& pos2, Scene* scene);

		//! map scalar variable to 1d colorscale
		static Vector3r scalarOnColorScale(Real x, Real xmin=0., Real xmax=1.);

		//! wrap floating number periodically to the given range
		static Real periodicWrap(Real x, Real x0, Real x1, long* period=NULL);

		//! Flip cell shear without affecting interactions; if flip is zeros, it will be computed such that abs of shear strain is minimal for each shear component
		//! Diagonal terms of flip are meaningless and ignored.
		static Matrix3r flipCell(const Matrix3r& flip=Matrix3r::Zero());

		//! Class for storing stresses, affected on bodies, obtained from Interactions
		struct bodyState{
				Vector3r normStress, shearStress;
				bodyState (){
					normStress = Vector3r(0.0,0.0,0.0);
					shearStress = Vector3r(0.0,0.0,0.0);
				}
		};
		//! Function of getting stresses for each body
		static void getStressForEachBody(vector<Shop::bodyState>&);
		
		//! Define the exact average stress in each particle from contour integral ("LW" stands for Love-Weber, since this is what the contour integral gives).
		static void getStressLWForEachBody(vector<Matrix3r>& bStresses);
		static py::list getStressLWForEachBody();
        
        //! Compute the dynamic stress tensor for each bodies;
		static py::list getDynamicStress();
		static Matrix3r getTotalDynamicStress(Real volume=0);
		
		//! Function to compute overall ("macroscopic") stress.
		static Matrix3r getStress(Real volume=0);
		static Matrix3r getCapillaryStress(Real volume=0, bool mindlin=false);
		static Matrix3r stressTensorOfPeriodicCell() { LOG_WARN("Shop::stressTensorOfPeriodicCelli is DEPRECATED: use getStress instead"); return Shop::getStress(); }

		//! Compute the stress tensor in each defined cell, return a stress tensor depth profile
		static py::tuple getStressProfile(Real volume, int nCell, Real dz, Real zRef, vector<Real> vPartAverageX, vector<Real> vPartAverageY, vector<Real> vPartAverageZ);
		static py::tuple getStressProfile_contact(Real volume, int nCell, Real dz, Real zRef);	//same, only contact contribution

        //! Compute average depth profile of particle velocity (x,y,z) and solid volume fraction. The direction may be specified by direction argument (default is z).
        static py::tuple getDepthProfiles(Real vCell, int nCell, Real dz, Real zRef,bool activateCond=false, Real radiusPy=0,int direction=2);
		//! Same, but taking into account point particles
		static py::tuple getDepthProfiles_center(Real vCell, int nCell, Real dz, Real zRef,bool activateCond=false, Real radiusPy=0);


		//! Compute overall ("macroscopic") stress of periodic cell, returning 2 tensors
		//! (contribution of normal and shear forces)
		static py::tuple normalShearStressTensors(bool compressionPositive=false, bool splitNormalTensor=false, Real thresholdForce=NaN);
		
		//! Function to compute fabric tensor
		static void fabricTensor(Real& Fmean, Matrix3r& fabric, Matrix3r& fabricStrong, Matrix3r& fabricWeak, Real cutoff=0.0, bool splitTensor=false, Real thresholdForce=NaN);
		static py::tuple fabricTensor(Real cutoff=0.0, bool splitTensor=false, Real thresholdForce=NaN);
		
		//! Function to set translational and rotational velocities of all bodies to zero
		static void calm(const shared_ptr<Scene>& rb=shared_ptr<Scene>(), int mask=-1);
		
		//! Get a list of body-ids, which contacts the given body;
		static py::list getBodyIdsContacts(Body::id_t bodyID=-1);

		//! Set material and contact friction to the given value, non-dynamic bodies are not affected
		static void setContactFriction(Real angleRad);

		//! Homothetic change of sizes of spheres and clumps
		static void growParticles(Real multiplier, bool updateMass, bool dynamicOnly);
		//! Change of size of a single sphere or a clump
		// DEPREC, update wrt growParticles()
		static void growParticle(Body::id_t bodyID, Real multiplier, bool updateMass);
		
		/* \todo implement groupMask */
		static py::tuple aabbExtrema(Real cutoff=0.0, bool centers=false);
		
		//! evaluation of 2D quantities
		static Real getSpheresVolume2D(const shared_ptr<Scene>& rb=shared_ptr<Scene>(), int mask=-1);
		static Real getVoidRatio2D(const shared_ptr<Scene>& rb=shared_ptr<Scene>(),Real zlen=1);
		//! get stress tensor and tangent operator tensor for FEMxDEM coupling. By Ning Guo
		static py::tuple getStressAndTangent(Real volume=0, bool symmetry=true);
		
		//! tests whether p lies in the (bbMin,bbMax) axis-aligned bounding box
		static bool isInBB(Vector3r p, Vector3r bbMin, Vector3r bbMax);
};

} // namespace yade

