/* Fesslix - Stochastic Analysis
 * Copyright (C) 2010-2016 Wolfgang Betz
 *
 * Fesslix is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fesslix is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Fesslix.  If not, see <http://www.gnu.org/licenses/>. 
 */

#ifndef fesslix_flxfem_elements_kl_H
#define fesslix_flxfem_elements_kl_H

#include "flxfem_elements.h"
#include "flxfem_GegenbauerPolyH_hard.h"

const tuint MAXTREEDEPTH = 20;	// maximal polynomial order of hierarchic legendre polynomials

inline void XXX_check_order(const tuint req_order) {
  #if FLX_USE_GEGENBAUERPOLY_FOR_KL
    hGBP_check_order(req_order);
  #else
    hLP_check_order(req_order);
  #endif
}

inline void XXX_vec(flxVec& r, const tdouble& x) {
  #if FLX_USE_GEGENBAUERPOLY_FOR_KL
    hGBP_vec(r,x);
  #else
    hLP_vec(r,x);
  #endif
}

inline void XXX_const(flxVec& r, const tdouble& x, const tdouble& f) {
  #if FLX_USE_GEGENBAUERPOLY_FOR_KL
    hGBP_const(r,x,f);
  #else
    hLP_const(r,x,f);
  #endif
}

inline const tdouble XXX_rlz(const tuint N, const tdouble& x, const tdouble& c0, const tdouble& c1, const tdouble* coordH) {
  #if FLX_USE_GEGENBAUERPOLY_FOR_KL
    return hGBP_rlz(N,x,c0,c1,coordH);
  #else
    return hLP_rlz(N,x,c0,c1,coordH);
  #endif
}


typedef std::map<const flxVertex*,tnlong> niMap;
struct eiMap_sec {
  tnlong dofindex;	// dofindex in the random field
  tuint count;		// pOrder-1
};
typedef std::map<const flxEdge*,eiMap_sec> eiMap;
struct fiMap_sec {
  tnlong dofindex;	// dofindex in the random field
  tuint count;		// number of internals DOFs of the face
};
typedef std::map<const flxFace*,fiMap_sec> fiMap;

class flxElement_KL_1D;
/**
* @brief the basic element type required for random fields
*/
class FLXFEM_EXPORT flxElement_KL_base : public FlxBoxBase2 {
  public:
    // Types of Random Fields (do NOT change the ordering)
      enum rftype { 
	rfKLfemGauss, rfKLfemGaussProj, rfKLAnaExpCorrGauss1D, rfColloGauss, rfEOLEgauss
      };
      
  protected:
    // static variables
      /**
      * @brief some pointers to global variables - for reasons of efficiency
      */
      static tdouble *GX;
      static tdouble *GY;
      static tdouble *GZ;
      static tdouble *GX2;
      static tdouble *GY2;
      static tdouble *GZ2;
      static const tdouble dha[20];
      
    /**
    * @brief true, if element has no geometry
    */
    bool is_empty;

    /**
    * @brief some pointers to data structures for faster access
    */
    FunBase* ptrFunSigma;
    FunBase* ptrFunRho;
    std::vector< flxVec >* ptrEigenvectors;
    flxVec* ptrEigenvalues;
    std::vector<flxElement_KL_base*>* ptrboxEL_vec;
    /**
    * @brief Functions which describes the geometry of the domain (geometry if >=0.0)
    */
    FlxFunction *geo_fun;
    
    /**
    * @brief updates global coordinates using local coordinates (for the OUTER integration loop !!!)
    */
    virtual void update_globalConst_o(const tdouble& lxV, const tdouble& lyV, const tdouble& lzV) const = 0;
    /**
    * @brief determines a point-wise error (w.r.t. errMeasure) ... for KL-FEM !!!
    */
    const tdouble get_error(const tdouble& lxV, const tdouble& lyV, const tdouble&lzV, const int errMeasure) const;
    /**
    * @brief determines the point-wise error of errMeasure=1 on THIs element (has to be done for every element)
    * @param tr the result (for the integral over this element)
    * @param tri helping-vector
    * make sure the global variables for the outer loop have been updated (already!)
    */
    virtual void get_error_1i_el(const bool sameEl, flxVec &tr, flxVec &tri) const = 0;
    /**
    * @brief determines the point-wise error of errMeasure=2
    * make sure the global variables for the outer loop have been updated (already!)
    */
    const tdouble get_error_2i(const tdouble &lxV, const tdouble &lyV, const tdouble &lzV, flxVec& tr) const;
      /**
      * @brief precomputes coefficients for the inner integration loop to determine cov_err
      */
      void get_error_2i_assemble_tr(const tuint DIM, const tdouble &lxV, const tdouble &lyV, const tdouble &lzV, flxVec& tr) const;
      /**
      * @brief performs the integration on THIS element (has to be done for every element!)
      */
      virtual void get_error_2i_el(const bool sameEl, tdouble &integ_Cov, tdouble &integ_Err, const tdouble &sDx, const flxVec& tr) const = 0;
      
    /**
    * @brief assembless Hx for EOLE ... using GX, GY, GZ
    */
    void assemble_Hx_EOLE(flxVec &Hx, tdouble &sD_curr_x, const std::vector<flxPoint> &pointVec, const flxVec &sdVec) const;
    virtual void get_error_2i_el_EOLE(const bool sameEl, tdouble &integ_Cov, tdouble &integ_Err, const tdouble &sDx, const flxVec& tr, const std::vector<flxPoint> &pointVec, const flxVec &sdVec, flxVec &Hx) const = 0;
  public:
    flxElement_KL_base(FlxFunction *geo_fun, const bool is_empty);
    virtual ~flxElement_KL_base();
    /**
    * @brief logs RF-el-information
    * @param elID e.g. 'edge EDGE-INDEX'
    */
    virtual void log_report(const tuint rfindex, const std::string& elID, const rftype rft) const = 0;
    virtual const tdouble calc_domain(const bool considerGeo=false) const = 0;
    /**
    * @brief 'collects' the DOFs in the field
    * @param nodalM nodal DOFs
    * @param edgeM edge DOFs
    */
    virtual void countDOFs(niMap &nodalM, eiMap &edgeM, fiMap &faceM, std::vector<const flxVertex*>& nV, std::vector<const flxEdge*>& eV, std::vector<const flxFace*>& fV ) = 0;
    /**
    * @brief assembles the DOFs of the corresponding field (assigns the mapping)
    */
    virtual void assembleDOFs(const niMap &nodalM, const eiMap &edgeM, fiMap &faceM) = 0;
    /**
    * @brief calculates the local mass matrix of the system and maps it to the global mass matrix
    * @param Mmtx the global mass matrix
    */
    virtual void calc_local_MassMatrix_KLE(FlxMtxSym &Mmtx) = 0;
    /**
    * @brief calculates the local correlation matrix of the system and maps it to the global correlation matrix
    */
    virtual void calc_local_corrMtx_KLE(FlxMtxSym &Cmtx, std::vector<flxElement_KL_base*> &ElVec) const = 0;
    /**
    * @brief calculates the local matrix needed by the collocation methods
    * @param Amtx the global matrix to assemble
    * @param nid the id of the node that is  currently assembled 
    */
    virtual void calc_local_collocMtx(const tnlong nid, FlxMtx& Amtx, const tdouble& sd) = 0;
    /**
    * @brief integrates (sum(d[i]*N[i](x)))^2
    * @param dVec is the GLOBAL coefficient vector
    */
    virtual const tdouble calc_djNj2(const tdouble* dVec) const = 0;
    /**
    * @brief calculates (sum(d[i]*N[i](x))) at position x
    * concerning efficiency: this function is the crucial function of the class !!!
    * @param dVec is the GLOBAL coefficient vector
    */
    virtual const tdouble calc_djNj(const tdouble* dVec, const tdouble &x, const tdouble &y, const tdouble&z) const = 0;
    /**
    * @brief determines the covariance error and integrates it over the domain
    */
    virtual const tdouble err_cov_domain() const = 0;
    /**
    * @brief determines the variance error and integrates it over the domain
    */
    virtual const tdouble err_var_domain() const = 0;
    /**
    * @brief determines the 'variance' error (difference of absolute value)
    */
    virtual const tdouble err_var_domain_sep() const;
    virtual void plot_el(std::ostream &sout, FlxMtxConstFun* ppdim, const int errMeasure, const int prec, const int fixW, const bool checkTOL) const = 0;
    virtual void plotcov_el(std::ostream &sout, const flxPoint& pp, FlxMtxConstFun* ppdim, const flxVec& tr, const int prec, const int fixW) const = 0;
    
    virtual const tuint guess_N_EOLEpoints() const = 0;
    virtual void assembleEOLEpointVec(std::vector<flxPoint> &pointVec, const tuint Npt=0, tdouble* Nia=NULL) const = 0;
    virtual void get_PlotPoints(std::vector<flxPoint> &pointVec, std::vector<tuint> &geoVec, FlxMtxConstFun* ppdim) const = 0;
    virtual const tdouble err_var_domain_EOLE(flxVec &Hx, const std::vector< flxPoint >& pointVec, const flxVec& sdVec, const bool abs) const = 0;
    /**
    * @brief determines the point-wise error of errMeasure=2
    * make sure the global variables for the outer loop have been updated (already!) i.e. GX=GX2,GY=GY2,GZ=GZ2
    */
    const tdouble get_error_2i_EOLE(flxVec &Hx, flxVec& tr, const std::vector< flxPoint >& pointVec, const flxVec& sdVec) const;
    /**
    * @brief determines the covariance error and integrates it over the domain (for EOLE)
    */
    virtual const tdouble err_cov_domain_EOLE(const std::vector< flxPoint >& pointVec, const flxVec& sdVec) const = 0;
    
    const tdouble calc_geo_fun() const { return (geo_fun==NULL)?1.0:geo_fun->calc(); } 
    const bool has_geometry() const { return geo_fun!=NULL; }
    /**
    * @brief maps a point to the local coordinate system of the elements
    * @param p a point in the global coordinate system
    * @return false, if the p is not within \Omega_element
    */
    virtual const bool map2local(const flxPoint& p) const = 0;
    
    void set_ptrFunSigma(FunBase* ptrFunSigmaV) { ptrFunSigma = ptrFunSigmaV; }
    void set_ptrFunRho(FunBase* ptrFunRhoV) { ptrFunRho = ptrFunRhoV; }
    void set_ptrEigenvectors(std::vector< flxVec >* ptrEigenvectorsV) { ptrEigenvectors = ptrEigenvectorsV; }
    void set_ptrEigenvalues(flxVec* ptrEigenvaluesV) { ptrEigenvalues = ptrEigenvaluesV; }
    void set_ptrBoxElvec(std::vector<flxElement_KL_base*>* ptrboxEL_vecV) { ptrboxEL_vec = ptrboxEL_vecV; }
    
    // static functions
      /**
      * @brief assign the pointers to global variables once - for reasons of efficiency
      */
      static void set_Const();
    
   friend class flxElement_KL_1D;
};

/**
* @brief the basic element type required for random fields
*/
class FLXFEM_EXPORT flxElement_KL_1D : public flxElement_KL_base {
  private:
    /**
    * @brief polynomial order of the ansatzfunctions
    */
    const tuint pOrder; 
    /**
    * @brief Defining Gaussian-Integration properties (
    */
    const tuint DegCorr;	// ... degree of the autocorrelation-function
    const tuint DegStd;		// ... degree of the standard deviation
    const tuint NGPi;		// ... number of Gauss points of the inner loop
    const tdouble* GPi;
    const tdouble* GWi;
    /**
    * @brief mapping table to the global DOFs of the field (0:node1;1:node2;3:startDOFofPORDER
    */
    tnlong mapGlobal[3];
    /**
    * @brief a reference to the parent element
    */
    flxEdge* edge;
    /**
    * @brief vectors storing the integration intervals (staggered Gaussian integration)
    */
    const std::vector<tdouble> IntervalVec1;		// inner loop, same element
    const std::vector<bool> IntervalVec1_geo;
    const std::vector<tdouble> IntervalVec2;		// inner loop, different elements
    const std::vector<bool> IntervalVec2_geo;
    const std::vector<tdouble> IntervalVecO;		// outer loop
    const std::vector<bool> IntervalVecO_geo;
    const std::vector<tdouble> IntervalVec_e2_i1;	// var-error, inner loop, same element
    const std::vector<bool> IntervalVec_e2_i1_geo;
    const std::vector<tdouble> IntervalVec_e2_i2;	// var-error, inner loop, different elements
    const std::vector<bool> IntervalVec_e2_i2_geo;
    const std::vector<tdouble> IntervalVec_e2_o;	// var-error, outer loop (computing global mean)
    const std::vector<bool> IntervalVec_e2_o_geo;
    const std::vector<tdouble> IntervalVec_Geo;		// contains just the geometry (without refinement)
    const std::vector<bool> IntervalVec_GeoB;
    
    /**
    * @brief update global constants (using local coordinates) - for the outer integration loop (C-matrix)
    */
    void update_globalConst_o(const tdouble& lxV) const { update_globalConst(lxV); *(GX+3) = *(GX+0); *(GX+4) = *(GX+1); *(GX+5) = *(GX+2); }
    /**
    * @brief update global constants (using local coordinates) - for the inner integration loop (C-matrix)
    */
    void update_globalConst_i(const tdouble& lxV) const { edge->get_vertex_1().get_point().set_global_dist(edge->get_vertex_2().get_point(),lxV/2.0+0.5); }
    virtual void update_globalConst_o(const tdouble& lxV, const tdouble& lyV, const tdouble& lzV) const { update_globalConst_o(lxV); }
    /**
    * @brief returns the maximal order of the shape functions to consider
    */
    const tuint max_deg_shape_function() const { return pOrder; }
    /**
    * @brief perform the inner integration loop
    */
    void calc_local_corrMtx_KLE_inner(flxVec &t, const bool sameElement) const;
    virtual void get_error_2i_el(const bool sameEl, tdouble &integ_Cov, tdouble &integ_Err, const tdouble &sDx, const flxVec& tr) const;
    virtual void get_error_1i_el(const bool sameEl, flxVec &tr, flxVec &tri) const;
    virtual void get_error_2i_el_EOLE(const bool sameEl, tdouble &integ_Cov, tdouble &integ_Err, const tdouble &sDx, const flxVec& tr, const std::vector<flxPoint> &pointVec, const flxVec &sdVec, flxVec &Hx) const;
  public:
    flxElement_KL_1D(flxEdge* edge, const tuint& pOrder, const tuint& DegCorr, const tuint& DegStd, const std::vector<tdouble>& IntervalVec1, const std::vector<bool>& IntervalVec1_geo, const std::vector<tdouble>& IntervalVec2, const std::vector<bool>& IntervalVec2_geo, const std::vector<tdouble>& IntervalVecO, const std::vector<bool>& IntervalVecO_geo, const std::vector<tdouble> &IntervalVec_e2_i1, const std::vector<bool> &IntervalVec_e2_i1_geo, const std::vector<tdouble> &IntervalVec_e2_i2, const std::vector<bool> &IntervalVec_e2_i2_geo, const std::vector<tdouble> &IntervalVec_e2_o, const std::vector<bool> &IntervalVec_e2_o_geo, const std::vector<tdouble> &IntervalVec_Geo, const std::vector<bool> &IntervalVec_GeoB, FlxFunction *geo_fun);
    /**
    * @brief update global constants (using local coordinates) (mass matrix)
    */
    void update_globalConst(const tdouble& lxV) const { edge->update_globalConst(lxV);  }
    /**
    * @brief calculates the absolute length of the element
    */
    const tdouble calc_length() const { return edge->calc_length(); }
    
    virtual void log_report(const tuint rfindex, const std::string& elID, const rftype rft) const;
    virtual const tdouble calc_domain(const bool considerGeo=false) const;
    virtual void countDOFs(niMap &nodalM, eiMap &edgeM, fiMap &faceM, std::vector<const flxVertex*>& nV, std::vector<const flxEdge*>& eV, std::vector<const flxFace*>& fV );
    virtual void assembleDOFs(const niMap &nodalM, const eiMap &edgeM, fiMap &faceM);
    virtual void calc_local_MassMatrix_KLE(FlxMtxSym &Mmtx);
    virtual void calc_local_corrMtx_KLE(FlxMtxSym &Cmtx, std::vector<flxElement_KL_base*> &ElVec) const;
    virtual void calc_local_collocMtx(const tnlong nid, FlxMtx& Amtx, const tdouble& sd);
    virtual const tdouble calc_djNj2(const tdouble* dVec) const;
    virtual const tdouble calc_djNj(const tdouble* dVec, const tdouble &x, const tdouble &y, const tdouble&z) const;
    virtual const tdouble err_cov_domain() const;
    virtual const tdouble err_var_domain() const;
    virtual const tdouble err_var_domain_sep() const;
    virtual void plot_el(std::ostream &sout, FlxMtxConstFun* ppdim, const int errMeasure, const int prec, const int fixW, const bool checkTOL) const;
    virtual void plotcov_el(std::ostream &sout, const flxPoint& pp, FlxMtxConstFun* ppdim, const flxVec& tr, const int prec, const int fixW) const;
    
    virtual const tuint guess_N_EOLEpoints()  const { return IntervalVecO.size(); }
    virtual void assembleEOLEpointVec(std::vector<flxPoint> &pointVec, const tuint Npt=0, tdouble* Nia=NULL) const;
    virtual void get_PlotPoints(std::vector<flxPoint> &pointVec, std::vector<tuint> &geoVec, FlxMtxConstFun* ppdim) const;
    virtual const tdouble err_var_domain_EOLE(flxVec &Hx, const std::vector< flxPoint >& pointVec, const flxVec& sdVec, const bool abs) const;
    virtual const tdouble err_cov_domain_EOLE(const std::vector< flxPoint >& pointVec, const flxVec& sdVec) const;
    virtual const bool map2local(const flxPoint& p) const;
    /**
    * @brief returns the interval vector for integrating the cov-error
    * @param idx (0: inner loop, same element; 2: inner loop, different elements;)
    */
    const std::vector<tdouble> &get_IntervalVec_err2(const int idx) const;
    const tuint get_pOcovOrder() const { return (pOrder>=DegCorr+DegStd*2)?pOrder:(DegCorr+DegStd*2); }
};

/**
* @brief the basic element type required for random fields
*/
class FLXFEM_EXPORT flxElement_KL_2D : public flxElement_KL_base {
  private:
    /**
    * @brief polynomial order of the ansatzfunctions
    */
    const tuint pOrderLx;
    const tuint pOrderLy;
    const bool tensorSpace;
    /**
    * @brief Defining Gaussian-Integration properties (
    */
    const tuint DegCorr;	// ... degree of the autocorrelation-function
    const tuint DegStd;		// ... degree of the standard deviation
    /**
    * @brief mapping table to the global DOFs of the field
    * 0,1,2,3: the four nodes
    * 4,5,6,7: the four edges
    * 8: startDOFofPORDER
    */
    tnlong mapGlobal[9];
    bool edgOrient[4];		// orientation of the edges
    tuint edgeDOFpOrder[4];	// polynomial order of the edge modes
    /**
    * @brief maximal polynomial order in local x and y direction
    */
    tuint pox; tuint poy;
    /**
    * @brief number of local DOFs of the system
    */
    tuint N_local_DOF;
    /**
    * @brief helping variables of djNj
    */
    mutable tdouble djNj_x;
    mutable tdouble djNj_y;
    flxVec* djNj_xV;
    flxVec* djNj_yV;
    flxVec* djNj_bV;
    /**
    * @brief a reference to the parent element
    */
    flxFace* face;
    /**
    * @brief information of the mesh
    */
    const tuint *mesh_single;
      tdouble *mesh_single_cc;
    const tuint *mesh_outer;
      tdouble *mesh_outer_cc;
    const tuint *mesh_inner_s;
      tdouble *mesh_inner_s_cc;
    const tuint *mesh_inner_d;
      tdouble *mesh_inner_d_cc;
    const tuint *mesh_err_is;
      tdouble *mesh_err_is_cc;
    const tuint *mesh_err_id;
      tdouble *mesh_err_id_cc;
    tuint maxtree_single;
    tuint maxtree_outer;
    tuint maxtree_inner_s;
    tuint maxtree_inner_d;
    mutable tuint maxtree_inner_now;
    const tuint area_min_level;
    const tuint area_max_level;
    const int EOLE_SMP_MTH;
    const tuint EOLE_N_LX;
    const tuint EOLE_N_LY;
    const tuint EOLE_N;
    const tdouble CovThreshold;
    
    /**
    * @brief update global constants (using local coordinates) - for the outer integration loop (C-matrix)
    */
    void update_globalConst_o(const tdouble& lxV, const tdouble& lyV) const { update_globalConst(lxV,lyV); *(GX+3) = *(GX+0); *(GX+4) = *(GX+1); *(GX+5) = *(GX+2); }
    /**
    * @brief update global constants (using local coordinates) - for the inner integration loop (C-matrix)
    */
    void update_globalConst_i(const tdouble& lxV, const tdouble& lyV) const;
    virtual void update_globalConst_o(const tdouble& lxV, const tdouble& lyV, const tdouble& lzV) const { update_globalConst_o(lxV,lyV); }
    /**
    * @brief perform the inner integration loop
    */
    #if FLX_KAHAN_FCRF
    void calc_local_corrMtx_KLE_inner(pVec &t, const bool sameElement) const;
    #else
    void calc_local_corrMtx_KLE_inner(flxVec &t, const bool sameElement) const;
    #endif
    /**
    * @brief returns the approx. degree of the function describing the inner integral
    */
    const tuint get_polydeg_innerIntegral_X() const { return DegCorr+DegStd+pox+(face->get_is_detJ_const()?0:1); }
    const tuint get_polydeg_innerIntegral_Y() const { return DegCorr+DegStd+poy+(face->get_is_detJ_const()?0:1); }
    virtual void get_error_2i_el(const bool sameEl, tdouble &integ_Cov, tdouble &integ_Err, const tdouble &sDx, const flxVec& tr) const;
    virtual void get_error_1i_el(const bool sameEl, flxVec &tr, flxVec &tri) const;
      void get_error_1i_el_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, flxVec &tr, flxVec &tri) const;
    virtual void get_error_2i_el_EOLE(const bool sameEl, tdouble &integ_Cov, tdouble &integ_Err, const tdouble &sDx, const flxVec& tr, const std::vector<flxPoint> &pointVec, const flxVec &sdVec, flxVec &Hx) const;
    
    /**
    * @brief 2D base of hierarchic polynomials (for this face)
    * @param res vector of size N_local_DOF
    */
    void XXX_2D_base(tdouble* res, const tdouble* rxp, const tdouble* ryp) const;
      void XXX_2D_bemc(tdouble* res,const tdouble* r1p, const tdouble& r2p, tuint i) const;	// calc the core of the ith edge mode
    /**
    * @brief realization of hierarchic Legendre polynomials in 2D (for this face)
    */
    const tdouble XXX_2D_rlz(const tdouble* rxp, const tdouble* ryp, const tdouble* d) const;
      const tdouble XXX_2D_emc(const tdouble* rp, const tdouble* de, tuint i) const;	// calc the core of the ith edge mode
    const tdouble calc_domain_rec(const tuint level, const tdouble xm, const tdouble ym) const;
    #if FLX_KAHAN_FCRF
    const pdouble calc_djNj2_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, const tdouble* dVec, flxVec& rx, flxVec& ry) const;
    #else
    const tdouble calc_djNj2_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, const tdouble* dVec, flxVec& rx, flxVec& ry) const;
    #endif
    #if FLX_KAHAN_FCRF
    void calc_local_corrMtx_KLE_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, flxElement_KL_2D* ce, flxpVec& d, flxVec& r, flxVec& s, flxpVec& t, flxVec& rx, flxVec& ry) const;
    #else
    void calc_local_corrMtx_KLE_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, flxElement_KL_2D* ce, flxVec& d, flxVec& r, flxVec& s, flxVec& t, flxVec& rx, flxVec& ry) const;
    #endif
    #if FLX_KAHAN_FCRF
    void calc_local_corrMtx_KLE_inner_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, flxpVec& t, flxVec& r, flxVec& s, flxVec& rx, flxVec& ry) const;
    #else
    void calc_local_corrMtx_KLE_inner_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, flxVec& t, flxVec& r, flxVec& s, flxVec& rx, flxVec& ry) const;
    #endif
    #if FLX_KAHAN_FCRF
    void calc_local_MassMatrix_KLE_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, flxpVec& M, flxVec& Mi, flxVec& rx, flxVec& ry) const;
    #else
    void calc_local_MassMatrix_KLE_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, flxVec& M, flxVec& Mi, flxVec& rx, flxVec& ry) const;
    #endif	
    const tdouble err_var_domain_EOLE_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, flxVec &Hx, const std::vector< flxPoint >& pointVec, const flxVec& sdVec, const bool abs) const;
    void update_GN(const tuint prev_level, const tuint max_level, tuint& GNx, tuint& GNy) const;
    void update_GN_mass(const tuint prev_level, const tuint max_level, tuint& GNx, tuint& GNy) const;
    void get_mesh_topology(const tuint level, const_tuintPtr& vpos, std::vector<tuint>& topV) const;
    /**
    * @brief checks if we have to integrate the element within the inner integration loop ... is the covariance value neglegible?
    */
    const tdouble check_cov_val() const;
  public:
    flxElement_KL_2D(flxFace* face, FlxKLelementProp_2D& KLelProp);
    ~flxElement_KL_2D();
    /**
    * @brief update global constants (using local coordinates) (mass matrix)
    */
    void update_globalConst(const tdouble& lxV, const tdouble& lyV) const { face->update_globalConst(lxV,lyV);  }
    /**
    * @brief calculates the absolute length of the element
    */
    const tdouble calc_area() const { return face->calc_area(); }
    
    virtual void log_report(const tuint rfindex, const std::string& elID, const rftype rft) const;
    virtual const tdouble calc_domain(const bool considerGeo=false) const;
    virtual void countDOFs(niMap &nodalM, eiMap &edgeM, fiMap &faceM, std::vector<const flxVertex*>& nV, std::vector<const flxEdge*>& eV, std::vector<const flxFace*>& fV );
    virtual void assembleDOFs(const niMap &nodalM, const eiMap &edgeM, fiMap &faceM);
    virtual void calc_local_MassMatrix_KLE(FlxMtxSym &Mmtx);
    virtual void calc_local_corrMtx_KLE(FlxMtxSym &Cmtx, std::vector<flxElement_KL_base*> &ElVec) const;
    virtual void calc_local_collocMtx(const tnlong nid, FlxMtx& Amtx, const tdouble& sd);
    virtual const tdouble calc_djNj2(const tdouble* dVec) const;
    virtual const tdouble calc_djNj(const tdouble* dVec, const tdouble &x, const tdouble &y, const tdouble&z) const;
    virtual const tdouble err_cov_domain() const;
    virtual const tdouble err_var_domain() const;
    virtual void plot_el(std::ostream &sout, FlxMtxConstFun* ppdim, const int errMeasure, const int prec, const int fixW, const bool checkTOL) const;
    virtual void plotcov_el(std::ostream &sout, const flxPoint& pp, FlxMtxConstFun* ppdim, const flxVec& tr, const int prec, const int fixW) const;
    
    virtual const tuint guess_N_EOLEpoints()  const { return EOLE_N_LX*EOLE_N_LY; }
    virtual void assembleEOLEpointVec(std::vector<flxPoint> &pointVec, const tuint Npt=0, tdouble* Nia=NULL) const;
    virtual void get_PlotPoints(std::vector<flxPoint> &pointVec, std::vector<tuint> &geoVec, FlxMtxConstFun* ppdim) const;
    virtual const tdouble err_var_domain_EOLE(flxVec &Hx, const std::vector< flxPoint >& pointVec, const flxVec& sdVec, const bool abs) const;
    virtual const tdouble err_cov_domain_EOLE(const std::vector< flxPoint >& pointVec, const flxVec& sdVec) const;
    virtual const bool map2local(const flxPoint& p) const;
};

/**
* @brief the basic element type required for random fields
*/
class FLXFEM_EXPORT flxElement_KL_3D : public flxElement_KL_base {
  private:
    /**
    * @brief polynomial order of the ansatzfunctions
    */
    tuint pOrder[3];
    /**
    * @brief Defining Gaussian-Integration properties (
    */
    const tuint DegCorr;	// ... degree of the autocorrelation-function
    const tuint DegStd;		// ... degree of the standard deviation
    /**
    * @brief number of local DOFs of the system
    */
    tuint N_local_DOF;
    /**
    * @brief maximal polynomial degree of the ansatzfunctions of the element
    */
    tuint pom;
    /**
    * @brief helping variables of djNj
    */
    mutable tdouble djNj_x[3];
    flxVec* djNj_xV[3];
    flxVec* djNj_bV;
    mutable tnlong MAXCOUNT;
    mutable tnlong NOWCOUNT; mutable tnlong LASTCOUNT;
    /**
    * @brief a reference to the parent element
    */
    flxBox* box;
    /**
    * @brief information of the mesh
    */
    const tuint *mesh_single;
      tdouble *mesh_single_cc;
    const tuint *mesh_outer;
      tdouble *mesh_outer_cc;
    const tuint *mesh_inner_s;
      tdouble *mesh_inner_s_cc;
    const tuint *mesh_err_is;
      tdouble *mesh_err_is_cc;
    tuint maxtree_single;
    tuint maxtree_outer;
    tuint maxtree_inner;
    const int EOLE_SMP_MTH;
    tuint EOLE_N[3];
    
    /**
    * @brief update global constants (using local coordinates) - for the outer integration loop (C-matrix)
    */
    void update_globalConst_o_fast(flxPoint& lxp) const { update_globalConst(lxp); *(GX+3) = *(GX+0); *(GX+4) = *(GX+1); *(GX+5) = *(GX+2); }
    /**
    * @brief update global constants (using local coordinates) - for the inner integration loop (C-matrix)
    */
    void update_globalConst_i(flxPoint& lxp) const;
    void update_globalConst_o(flxPoint& lxp) const { update_globalConst_o_fast(lxp); }
    virtual void update_globalConst_o(const tdouble &lx, const tdouble &ly, const tdouble &lz) const { flxPoint lxp(lx,ly,lz); update_globalConst_o(lxp); }
    /**
    * @brief perform the inner integration loop
    */
    void calc_local_corrMtx_KLE_inner(flxVec &t, flxVec** rv) const;
    
    virtual void get_error_2i_el(const bool sameEl, tdouble &integ_Cov, tdouble &integ_Err, const tdouble &sDx, const flxVec& tr) const;
    virtual void get_error_1i_el(const bool sameEl, flxVec &tr, flxVec &tri) const;
      void get_error_1i_el_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, const tuint* GN, flxPoint& p, flxVec &tr, flxVec &tri) const;
    virtual void get_error_2i_el_EOLE(const bool sameEl, tdouble &integ_Cov, tdouble &integ_Err, const tdouble &sDx, const flxVec& tr, const std::vector<flxPoint> &pointVec, const flxVec &sdVec, flxVec &Hx) const;
    
    /**
    * @brief 3D base of hierarchic polynomials (for this box)
    * @param res vector of size N_local_DOF
    */
    void XXX_3D_base(tdouble* res, const tdouble** rvp) const;
    /**
    * @brief realization of hierarchic Legendre polynomials in 3D (for this box)
    */
    const tdouble XXX_3D_rlz(const tdouble** rvp, const tdouble* d) const;
    const pdouble calc_domain_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, flxPoint& p) const;
    const tdouble calc_djNj2_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, const tuint* GN, flxPoint& p, const tdouble* dVec, flxVec** rv) const;
    void calc_local_corrMtx_KLE_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, const tuint* GN, flxPoint& p, flxVec& d, flxVec& r, flxVec& s, flxVec& t, flxVec** rv, flxVec** rv2, FlxProgress &prg) const;
    void calc_local_corrMtx_KLE_inner_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, const tuint* GN, flxPoint& p, flxVec& t, flxVec& r, flxVec& s, flxVec** rv) const;
    void calc_local_MassMatrix_KLE_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, const tuint* GN, flxPoint& p, flxVec& M, flxVec& Mi, flxVec** rv) const;
    const tdouble err_var_domain_EOLE_rec(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, const tuint* GN, flxPoint& p, flxVec &Hx, const std::vector< flxPoint >& pointVec, const flxVec& sdVec, const bool abs) const;
    void update_GN(const tuint prev_level, tuint *GN) const;
    void update_GN_mass(const tuint prev_level, tuint *GN) const;
    void get_mesh_topology(const tuint level, const_tuintPtr& vpos, std::vector<tuint>& topV) const;
  public:
    flxElement_KL_3D(flxBox* box, FlxKLelementProp_3D& KLelProp);
    ~flxElement_KL_3D();
    /**
    * @brief update global constants (using local coordinates) (mass matrix)
    */
    void update_globalConst(flxPoint& lxp) const { box->update_globalConst(lxp);  }
    /**
    * @brief calculates the absolute length of the element
    */
    const tdouble calc_volume() const { return box->calc_volume(); }
    
    virtual void log_report(const tuint rfindex, const std::string& elID, const rftype rft) const;
    virtual const tdouble calc_domain(const bool considerGeo=false) const;
    virtual void countDOFs(niMap &nodalM, eiMap &edgeM, fiMap &faceM, std::vector<const flxVertex*>& nV, std::vector<const flxEdge*>& eV, std::vector<const flxFace*>& fV );
    virtual void assembleDOFs(const niMap &nodalM, const eiMap &edgeM, fiMap &faceM);
    virtual void calc_local_MassMatrix_KLE(FlxMtxSym &Mmtx);
    virtual void calc_local_corrMtx_KLE(FlxMtxSym &Cmtx, std::vector<flxElement_KL_base*> &ElVec) const;
    virtual void calc_local_collocMtx(const tnlong nid, FlxMtx& Amtx, const tdouble& sd);
    virtual const tdouble calc_djNj2(const tdouble* dVec) const;
    virtual const tdouble calc_djNj(const tdouble* dVec, const tdouble &x, const tdouble &y, const tdouble&z) const;
    virtual const tdouble err_cov_domain() const;
    virtual const tdouble err_var_domain() const;
    virtual void plot_el(std::ostream &sout, FlxMtxConstFun* ppdim, const int errMeasure, const int prec, const int fixW, const bool checkTOL) const;
    virtual void plotcov_el(std::ostream &sout, const flxPoint& pp, FlxMtxConstFun* ppdim, const flxVec& tr, const int prec, const int fixW) const;
    
    virtual const tuint guess_N_EOLEpoints()  const { return EOLE_N[0]*EOLE_N[1]*EOLE_N[2]; }
    virtual void assembleEOLEpointVec(std::vector<flxPoint> &pointVec, const tuint Npt=0, tdouble* Nia=NULL) const;
    virtual void get_PlotPoints(std::vector<flxPoint> &pointVec, std::vector<tuint> &geoVec, FlxMtxConstFun* ppdim) const;
    virtual const tdouble err_var_domain_EOLE(flxVec &Hx, const std::vector< flxPoint >& pointVec, const flxVec& sdVec, const bool abs) const;
    virtual const tdouble err_cov_domain_EOLE(const std::vector< flxPoint >& pointVec, const flxVec& sdVec) const;
    virtual const bool map2local(const flxPoint& p) const;
};




class FLXFEM_EXPORT FlxKLelementProp_base : public FlxReaderBase2, public FlxBoxBase2 {
  protected:
    /**
    * @brief geometrical function (>= 1 :=> geometry)
    */
    FlxFunction* geo_fun;
    /**
    * @brief an approximate polynomial degree of the autocorrelation function
    */
    FlxFunction* degCorr;
    /**
    * @brief an approximate polynomial degree of the standard deviation
    */
    FlxFunction* degStd;
    
    void read_fun(FlxFunctionPtr& fp,const std::string str) const;
  public:
    FlxKLelementProp_base();
    virtual ~FlxKLelementProp_base();
    const tuint get_degCorr();
    const tuint get_degStd();
    
    /**
    * @brief returns a copy of the geo-function
    */
    FlxFunction* get_geo_fun() const { return ((geo_fun==NULL)?NULL:(new FlxFunction(*geo_fun))); }
};

/**
* @brief stores properties to create KL-elements (1D-elements !!!)
*/
class FLXFEM_EXPORT FlxKLelementProp_1D : public FlxKLelementProp_base {
  private:
    /**
    * @brief the maximum interval size for integration on the same element (in percentage) - inner loop
    */
    FlxFunction* maxsize_1;
    /**
    * @brief the maximum interval size for integration not on the same element (in percentage) - inner loop
    */
    FlxFunction* maxsize_2;
    /**
    * @brief the minimum interval size (in percentage)
    */
    FlxFunction* minsize;
    /**
    * @brief the maximum interval size for integration (in percentage) - outer loop
    */
    FlxFunction* maxsize_out;
    /**
    * @brief the maximum interval size for obtaining the cov_error (integration on the same element)
    */
    FlxFunction* maxsize_err2_1;
    /**
    * @brief the maximum interval size for obtaining the cov_error (integration on different elements)
    */
    FlxFunction* maxsize_err2_2;
    /**
    * @brief the maximum interval size for integrating the cov_error (computing global mean error)
    */
    FlxFunction* maxsize_err2_o;
    
    /**
    * @brief maximum order of the shape functions of the element
    */
    FlxFunction* pOrder;
    
    /**
    * @brief Number of starting intervals to detect the geometry 
    */
    FlxFunction* geo_intnumb;
    /**
    * @brief minimum interval size (in percent) for stopping the detection of geometrical boundaries
    */
    FlxFunction* geo_imin;
    
    /**
    * @brief vector storing the geometry information - after do_geo_refine has been executed
    */
    std::vector<tdouble> geo_vector;
    std::vector<bool> geo_vector_bool;
  public:
    FlxKLelementProp_1D(bool errSerious=true);
    ~FlxKLelementProp_1D();
    const tuint get_pOrder();
    /**
    * @brief performs the initial refinement w.r.t. geometry 
    */
    const bool do_geo_refine_1D(const flxEdge& klbel);
    /**
    * @brief create the intervals
    * @param idx (0: inner loop, same element; 1: inner loop, different elements; 2: outer loop;)
    */
    void get_refine_1D( const int idx, std::vector<tdouble> &resVec, std::vector<bool> &resVecGeo);
    /**
    * @brief refines a vector - w.r.t. cov-error intervals
    * @param idx (0: inner loop, same element; 1: inner loop, different elements; 2: outer loop;)
    */
    void refine_vec( const int idx, std::vector<tdouble> &resVec, std::vector<bool> &resVecGeo, const std::vector<tdouble> &iVec, const std::vector<bool> &iVecb);
    /**
    * @brief returns just the geometrical-informations without refinement (exact in 1D)
    */
    void get_geo_info(std::vector<tdouble> &resVec, std::vector<bool> &resVecGeo) const;
    
};

/**
* @brief stores properties to create KL-elements (2D-elements !!!)
*/
class FLXFEM_EXPORT FlxKLelementProp_2D : public FlxKLelementProp_base {
  private:
    const flxFace* face;
    /**
    * @brief maximum order of the shape functions of the element
    */
    FlxFunction* pOrderLx;
    FlxFunction* pOrderLy;
    bool tensorSpace;
    FlxFunction* area_min_level;
    FlxFunction* area_max_level;
    FlxFunction* EOLE_SMP_MTH;
    FlxFunction* EOLE_N_LX;
    FlxFunction* EOLE_N_LY;
    FlxFunction* EOLE_N;
    FlxFunction* integ1l_min_level;
    FlxFunction* integ1l_max_level;
    FlxFunction* outer_min_level;
    FlxFunction* outer_max_level;
    FlxFunction* inner_s_min_level;
    FlxFunction* inner_s_max_level;
    FlxFunction* inner_d_min_level;
    FlxFunction* inner_d_max_level;
    FlxFunction* err_is_min_level;
    FlxFunction* err_is_max_level;
    FlxFunction* err_id_min_level;
    FlxFunction* err_id_max_level;
    FlxFunction* cellAreaPoints;
    FlxFunction* covThreshold;
    
    void refine_mesh_help(std::vector<tuint> &vec, const tuint level, const tuint min_level, const tuint max_level, const tdouble xm, const tdouble ym, std::vector<tdouble>& cutcells);
      void check_cell(const tdouble dhk, const tdouble xm, const tdouble ym, bool& same, bool *gb);
      const tdouble get_cellarea(const tdouble dh, const tdouble xm, const tdouble ym, tdouble& cmx, tdouble& cmy);
    const tuint* refine_mesh(const tuint min_level, const tuint max_level, tdoublePtr& cutcells);
    void delete_dyn();
  public:
    FlxKLelementProp_2D(const bool errSerious=true);
    ~FlxKLelementProp_2D();
    const bool get_tensorSpace() { return tensorSpace; }
    const tuint get_pOrderLx();
    const tuint get_pOrderLy();
    const tuint get_area_min_level();
    const tuint get_area_max_level();
    const int get_EOLE_SMP_MTH();
    const tuint get_EOLE_N_LX();
    const tuint get_EOLE_N_LY();
    const tuint get_EOLE_N();
    void assign_maxtree_inner(tuint& maxtree_inner_s,tuint& maxtree_inner_d);
    const tuint* refine_single(tuint &maxtree_single,tdoublePtr& cutcells);
    const tuint* refine_outer(tuint &maxtree_outer,tdoublePtr& cutcells);
    const tuint* refine_inner_s(tuint &maxtree_inner,tdoublePtr& cutcells);
    const tuint* refine_inner_d(tuint &maxtree_inner,tdoublePtr& cutcells);
    const tuint* refine_err_is(tdoublePtr& cutcells);
    const tuint* refine_err_id(tdoublePtr& cutcells);
    const tdouble get_covThreshold();
    
    void set_face(const flxFace* faceV);
};

/**
* @brief stores properties to create KL-elements (2D-elements !!!)
*/
class FLXFEM_EXPORT FlxKLelementProp_3D : public FlxKLelementProp_base {
  private:
    const flxBox* boxV;
    /**
    * @brief maximum order of the shape functions of the element
    */
    FlxMtxConstFun* pOrder;
    FlxFunction* EOLE_SMP_MTH;
    FlxMtxConstFun* EOLE_N;
    FlxFunction* integ1l_min_level;
    FlxFunction* integ1l_max_level;
    FlxFunction* outer_min_level;
    FlxFunction* outer_max_level;
    FlxFunction* inner_s_min_level;
    FlxFunction* inner_s_max_level;
    FlxFunction* inner_d_min_level;
    FlxFunction* inner_d_max_level;
    FlxFunction* err_is_min_level;
    FlxFunction* err_is_max_level;
    FlxFunction* err_id_min_level;
    FlxFunction* err_id_max_level;
    FlxFunction* cellVolumePoints;
    
    void refine_mesh_help(std::vector<tuint> &vec, const tuint level, const tuint min_level, const tuint max_level, const flxPoint& p, std::vector<tdouble>& cutcells);
      void check_cell(const tdouble dhk, const flxPoint& p, bool& same, bool *gb);
      const tdouble get_cellvolume(const tdouble dh, flxPoint& cm);
    const tuint* refine_mesh(const tuint min_level, const tuint max_level, tdoublePtr& cutcells);
    void delete_dyn();
  public:
    FlxKLelementProp_3D(const bool errSerious=true);
    ~FlxKLelementProp_3D();
    void get_pOrder(tuint* ref);
    const int get_EOLE_SMP_MTH();
    void get_EOLE_N(const int SMP_MTH, tuint* ref);
    const tuint* refine_single(tuint &maxtree_single,tdoublePtr& cutcells);
    const tuint* refine_outer(tuint &maxtree_outer,tdoublePtr& cutcells);
    const tuint* refine_inner_s(tuint &maxtree_inner,tdoublePtr& cutcells);
    const tuint* refine_err_is(tdoublePtr& cutcells);
    
    void set_box(const flxBox* boxVv);
};


#endif // fesslix_flxfem_elements_kl_H
