/* 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_geometry_H
#define fesslix_flxfem_geometry_H

#include "flxfem_load.h"

template<class _Signature >
class function;
class flxVertexList;
typedef flxDOFn* flxDOFnPtr;
/**
* @brief DOF for a node
*/
class FLXFEM_EXPORT flxDOFn {
  private: 
    flxDOFn* next;
    const FlxDof::dof dofDir;
    FlxDof::dof_fixed_state state;
    tnlong DOFindex;			// this value may only be changed by 'assembleDOF'
    void* state_value;
    
    /**
    * @brief finds the position of the element within the linked list
    * @return link to the next element
    */
    flxDOFn* link2list(flxDOFn* el2link);
    /**
    * @brief finds a certain DOF
    * @return NULL if DOF is not found
    */
    flxDOFn* find_dof(const FlxDof::dof dofDirV);
    void print_dof(std::ostream& sout, const flxFEM& FEM) const;
    void get_mappingVector(tnlong* mapVec, bool* hasMap);
    /**
    * @brief counts how many DOFs exist
    * @param DofA Number of DOFs which are not fixed
    * @param FixA Number of DOFs which are fixed
    * @param preDisA Number of DOFs which are fixed and have a predescribed displacement
    * @param DofSum Number of DOFs (fixed and not fixed)
    */
    void countDOF(tnlong& DofA, tnlong& FixA, tnlong& preDispA, tnlong& DofSum) const;
    /**
    * @brief assembles all DOFs -> assign an index to every DOF
    * @param DofIndex the current index of all free DOFs
    * @param FixPreIndex the current index of all fixed DOFs with a nonzero predisplacement
    * @param FixZeroIndex the current index of all DOFs fixed DOFs with zero prediplacement
    */
    void assembleDOF(tnlong& DofIndexFree, tnlong& FixPreIndex, tnlong& FixZeroIndex);
    void assembleStf(flxStiffnessMtxBase& stfmtx);
  public:
    flxDOFn(const FlxDof::dof dofDir, const FlxDof::dof_fixed_state state, FlxFunction* state_valueV, flxDOFnPtr& start, flxVertexList& vl);
    ~flxDOFn();
    const tnlong get_DOFindex() const { return DOFindex; }
    const FlxDof::dof get_dofDir() const { return dofDir; }
    const FlxDof::dof_fixed_state get_state() const { return state; }
    const tdouble get_state_value() const;
    
    friend class flxVertex;
    friend class flxNLoad;
    friend class FlxObjImportData_ElPlaneStr;
};

/**
* @brief a FEM-node
*/
class FLXFEM_EXPORT flxVertex {
  private:
    const flxPoint p;
    flxDOFn* dofList;
    flxNLoad* loadList;
    static flxVertexList* vl;
    
    void print_dof(std::ostream& sout, const flxFEM& FEM) const;
    void countDOF(tnlong& DofA, tnlong& FixA, tnlong& preDispA, tnlong& DofSum) const { if (dofList) dofList->countDOF(DofA,FixA,preDispA,DofSum); }
    void assembleDOF(tnlong& DofIndexFree, tnlong& FixPreIndex, tnlong& FixZeroIndex)  { if (dofList) dofList->assembleDOF(DofIndexFree,FixPreIndex,FixZeroIndex); }
    void assembleStf(flxStiffnessMtxBase& stfmtx);
    void assembleForces(flxStiffnessMtxBase& stfmtx) const;
  public:
    flxVertex(const flxPoint& pV, flxDOFn* dofList) : p(pV), dofList(dofList), loadList(NULL) {};
    ~flxVertex();
    const flxPoint& get_point() const { return p;};
    /**
    * @brief finds the requested DOF; if do_create: create if DOF does not exist - otherwise throw an error (if 'throwError') - otherwise return NULL
    */
    flxDOFn* find_dof(const FlxDof::dof dofDir, const bool do_create, const bool throwError=true);
    /**
    * @brief inserts a nodal loadList
    * @param ndof direction
    * @param value the value of the loadList
    * @param lc the load case of the load
    * @param replace true: replace already existing load; false: add an already existing load
    */
    void insert_Nload(const FlxDof::dof ndof, FlxFunction* value, const FlxLoadCase &lc, const bool replace);
    /**
    * @brief fixes the nodal DOFs in a certain direction ... linked by a transformation matrix
    * @param DOFnMap the topology of the transformation matrix
    * @param dofV the direction (in the local coordinate system)
    * @param preDispl NULL: fixed; !NULL: prediplacement
    */
    void fix_DOFs(const bool* DOFnMap, const FlxDof::dof dofV, FlxFunction* preDispl);
    /**
    * @brief links the nodal DOFs of THIS node in a certain direction ... linked by a transformation matrix
    * @param DOFnMap the topology of the transformation matrix
    * @param dofV the direction (in the local coordinate system)
    * @param vtx the vertex to which to link to
    */
    void link_DOFs(const bool* DOFnMap, const FlxDof::dof dofV, flxVertex& vtx);
    /**
    * @brief returns the mapping vector of existing DOFs - structure: [DofDir]=globalDOFindex
    * @note DofDir-entries which are not in the dofList, are note defined !!!
    * @param mapVec mapping vector; length=6
    * @param hasMap true, if index has a map; length=6; optional
    */
    void get_mappingVector(tnlong* mapVec, bool *hasMap=NULL);
    
    friend class flxVertexList;
};

/**
* @brief contains the global nodes of a FEM problem
*/
class FLXFEM_EXPORT flxVertexList {
  private:
    /**
    * @brief true, if the topology is locked
    */
    bool locked;
    std::map<tuint, flxVertex*> box;
    
    #if FLX_DEBUG
    static int instance;
    #endif

    void countDOF(tnlong& DofA, tnlong& FixA, tnlong& preDispA, tnlong& DofSum);
    void assembleDOF(tnlong& DofIndexFree, tnlong& FixPreIndex, tnlong& FixZeroIndex);
    void assembleStf(flxStiffnessMtxBase& stfmtx);
    void assembleForces(flxStiffnessMtxBase& stfmtx) const;
  public:
    flxVertexList();
    ~flxVertexList();
    const bool is_locked() { return locked; }
    
    /**
    * @brief insert a node into the list
    */
    void insert (const tuint index, flxVertex* vertex, const bool errSerious=true);
    /**
    * @brief findes the node with index 'index' -> if node does not exist, an error is thrown if 'throwErr'
    */
    flxVertex* get(const tuint index, const bool throwErr);
    /**
    * @brief returns the index of the corresponding node
    */
    const tuint get_index(const flxVertex* const node) const;
    /**
    * @brief finds the node with the corresponding DOF ... otherwise return NULL
    */
    flxVertex* find(const flxDOFn* DOF);
    /**
    * @brief prints the index and the point of the node
    * @param printSolution true: print the solution as well
    * @param FEM only required, if printSolution=true
    */
    void print(std::ostream& sout, const bool printSolution=false, const flxFEM* FEM=NULL) const;
    
    friend class flxStiffnessMtxBase;
};

class flxEdge;
class flxEdgeList;
class flxDOFe;
typedef flxDOFe* flxDOFePtr;
/**
* @brief DOF for an edge
*/
class FLXFEM_EXPORT flxDOFe : protected FlxBoxBaseR {
  private: 
    flxDOFe* next;
    const FlxDof::dof dofDir;		// node in the local coordinate system
    FlxDof::dof_fixed_state state;
    bool internalDOF;			// if 'true', this DOFs are local to this edge (linked faces, linked edges cannot be internal)
    tuint DOFpOrder;			// number of DOFs in this edge DOF (pOrder-1) -> SHOULD NOT BE CHANGED !!!
    tnlong DOFindex_internal;		
    tnlong DOFindex;			// this value may only be changed by 'assembleDOF'
    void* state_valueD;
    
    static tdouble *LX;
    
    /**
    * @brief finds the position of the element within the linked list
    * @return link to the next element
    */
    flxDOFe* link2list(flxDOFe* el2link);
    /**
    * @brief finds a certain DOF
    * @return NULL if DOF is not found
    */
    flxDOFe* find_dof(const FlxDof::dof dofDirV);
    /**
    * @brief returns true, if the edge has only wx,wy or wz as DOFs
    */
    const bool has_only_displDOFs() const;
    /**
    * @brief determines which nodal DOFs are required to link the edge
    * @param DOFnMap topology of the transformation matrix (array of size 9)
    * @param reqMap bool-array : true->nodal DOF is required; (array of size 6)
    */
    void requires_nodal_DOF(const bool* DOFnMap, bool* reqMap) const;
    /**
    * @brief counts how many DOFs exist
    * @param DofA Number of DOFs which are not fixed
    * @param FixA Number of DOFs which are fixed
    * @param preDisA Number of DOFs which are fixed and have a predescribed displacement
    * @param DofSum Number of DOFs (fixed and not fixed)
    * @param Ninter number of intervals on the edge
    */
    void countDOF(tnlong& DofA, tnlong& FixA, tnlong& preDispA, tnlong& DofSum, tuint& i_DofA, tuint& LinkDofA, const bool condenseDOF, const tuint Nintervals, const flxEdgeList& egl, const flxEdge& currentEdge);
    /**
    * @brief assembles all DOFs -> assign an index to every DOF
    * @param DofIndex the current index of all free DOFs
    * @param FixPreIndex the current index of all fixed DOFs with a nonzero predisplacement
    * @param FixZeroIndex the current index of all DOFs fixed DOFs with zero prediplacement
    */
    void assembleDOF(tnlong& DofIndexFree, tnlong& FixPreIndex, tnlong& FixZeroIndex, const bool condenseDOF, tuint &iDOF_external, tuint &iDOF_internal, const tuint Nintervals);
    void assembleStf(flxEdge &edge, const tdouble* meshInterval, const tuint meshInterval_size);
  public:
    flxDOFe(const FlxDof::dof dofDir, const FlxDof::dof_fixed_state state, const tuint DOFpOrderV, FlxFunDeg* state_valueDV, flxDOFePtr& start, const flxEdgeList& egl);
    ~flxDOFe();
    const tnlong get_DOFindex() const { return DOFindex; }
    const tnlong get_DOFindex_internal() const { return DOFindex_internal; }
    const FlxDof::dof get_dofDir() const { return dofDir; }
    const tuint get_DOFpOrder() const { return DOFpOrder; }
    void unset_internal() { internalDOF=false; }
    const tuint get_max_pOrder() const;
    
    static void set_Const(FlxConstantBox& ConstantBox);
    
    friend class flxEdge;
};

/**
* @brief controls the mesh-refinement of a 1D-element
*/
class FLXFEM_EXPORT flxFEMrefine_1D : public FlxReaderBase2 {
  private:
    bool do_refine;
    bool do_loadrefine;
    FlxFunction* minF;
    FlxFunction* maxF;
    std::vector<FlxFunction**> sections;	// a vector with columns 3 entries
    std::vector<tdouble> mesh;
    void delete_sections();
  public:
    flxFEMrefine_1D(bool errSerious=true);
    flxFEMrefine_1D(bool do_refine, bool do_loadrefine, FlxFunction* minF, FlxFunction* maxF);
    flxFEMrefine_1D(const flxFEMrefine_1D& ref);
    ~flxFEMrefine_1D();
    /**
    * @brief deletes the mesh
    */
    void clear();
    flxFEMrefine_1D& operator=(const flxFEMrefine_1D& ref);
    const bool get_do_refine() const { return do_refine; }
    const bool get_do_loadrefine() const { return do_refine && do_loadrefine; }
    /**
    * @brief insert a new mesh-point (and returns its index)
    */
    const tuint insert_point(const tdouble& lx);
    /**
    * @brief performs the refinement procedure
    */
    void refine();
    /**
    * @brief returns the number of elements
    */
    const tuint get_element_number() const { return mesh.size()-1; }
    /**
    * @brief copies the geometry information to a vector
    */
    void copy_geometry(tdouble *v) const;
    
    const std::string write();
    
    /**
    * @brief search in which interval lx is - and returns the index of the starting mesh-point
    * @param index the index of the starting mesh-point of the interval (sub-element index)
    * @param lx the coordinate of which an interval is to be determined [-1;1]
    * @param mp a pointer to the first element in the mesh
    * @param N length of the mesh-vector (number of intervals)
    */
    static const bool search_interval(tuint& index, const tdouble& lx, const tdouble* mp, const tuint N);
    /**
    * @brief returns the local coordinate of the sub-element
    * @param index the index of the starting mesh-point of the interval (sub-element index)
    * @param lx the coordinate of the edge - as to be in sub-element 'index'  (NOT checked)
    * @param mp a pointer to the first element in the mesh
    */
    static const tdouble get_lx_sub(const tuint index, const tdouble& lx, const tdouble* mp) { return (2.0*lx-mp[index]-mp[index+1])/(mp[index+1]-mp[index]); }
};

class FLXFEM_EXPORT flxDomain_base : public FlxBoxBaseR {
  protected:
    static tdouble *LX;
    static tuint countID;
  public:
    virtual ~flxDomain_base() {}
    const tuint defID;
    
    flxDomain_base() : defID(countID++) {  }
    enum geoel_type { 
       geoel_edge, geoel_face, geoel_box
    };
    static const geoel_type get_geoel_type(const std::string& str, const bool errSerious = true);
    static const std::string write(const geoel_type gt);
    static void set_Const(FlxConstantBox& ConstantBox);
};

class flxElement_1D_base;
typedef flxElement_1D_base* flxElement_1D_Ptr;
class flxElement_KL_base;
class FlxKLelementProp_1D;
/**
* @brief a FEM-edge
* Structure of the local edge stiffness matrix & Co.
* NODAL_PART EDGE_X_PART EDGE_X_PART ... EDGE_X_PART
* NODAL_PART = wx1,wy1,wz1,wx2,wy2,wz2 [rx1,ry1,rz1,rx2,ry2,rz2]
* EDGE_X_PART = PORD_EX n2 PORD_EX n3 ... PORD_EX  ; nX-> values of intermetiade points
*   length of 'PORD_EX n2' = pOrder
* PORD_EX = d1,d2,...dn
*/
class FLXFEM_EXPORT flxEdge : public flxDomain_base {
  private:
    flxVertex& vertex1;
    flxVertex& vertex2;
    flxDOFe* dofList;
    flxElement_1D_base* elList;
    flxELoad* loadList;
    tdouble* meshInterval;		// geometry (interfal information) ... (local) coordinates of the mesh
    tuint meshInterval_size;		// number of intervals
    bool DOFnMap[9];			// topology of the transformation matrix
    bool reqDOFn[6];			// the global DOFs required for this element
    const bool condenseDOF;		// true: internal DOFs are condensed
    tuint default_order;
    tuint DOFe_start;			// either 6 or 12 ... number of nodal DOFs (transformation required)
    tuint DOFe_external;		// number of external edge-DOFs
    tuint DOFe_internal;		// number of internal edge-DOFs
    flxVec* eStfMtx;			// stiffness matrix of the edge -> NULL if no internal DOFs exist (symmetric matrix)
    flxVec* eForceVec;			// force vector of the edge -> NULL if no internal DOFs exist
    flxVec* eDisplVec;			// displacement vector of the edge -> NULL if elList=NULL
    flxFEMrefine_1D* mesh;		// the refinement-information ... CAN be NULL
    tuint DegStd;			// KL-integration: degree standard deviation function
    tuint DegCorr;			// KL-integration: degree correlation coefficient function
    tuint KLmesh_i;			// KL-integration (inner)
    tuint KLmesh_o;			// KL-integration (outer)
    static flxEdgeList* egl;
    
    /**
    * @brief detects a certain index within the stiffness matrix or force vector
    * @see update_local_StfMtx
    * @param elNumb counting starts with 0 !!!
    */
    const tuint get_localIndex(const tuint elNumb, const flxDOFe* const dofe, const tuint dofID) const;
    
    /**
    * @brief assigns 'meshInterval' and deletes 'mesh'
    */
    void refineMesh();
    void countDOF(tnlong& DofA, tnlong& FixA, tnlong& preDispA, tnlong& DofSum, flxVertexList &vl, tuint& tmpVec_size);
    void assembleDOF(tnlong& DofIndexFree, tnlong& FixPreIndex, tnlong& FixZeroIndex);
    void assembleStf(flxStiffnessMtxBase& stfmtx, flxVec* tmpVec);
    void assembleForces(flxStiffnessMtxBase& stfmtx, flxVec* tmpVec);
    void updateDisplVec(const flxStiffnessMtxBase& stfmtx);
    void KL_integ_2im(const tuint npx, const tuint npy, tdouble* Mtp, const flxVec& s, flxVec& t, flxVec& st) const;
    void KL_integ_2iv(const tuint npx, const tuint npy, const flxVec& s, flxVec& t, flxVec& h, flxVec& ht, const tdouble sD1, FlxFunction* sD, FlxFunction* rho, const tdouble* ni, const tdouble* e1p, const tdouble* e2p, const tdouble* e3p, const tdouble* e4p) const;
  public:
    flxEdge(flxVertex& vertex1, flxVertex& vertex2, flxDOFe* dofList, flxFEMrefine_1D* meshV, const tuint default_order, const bool condenseDOF);
    ~flxEdge();
    flxVertex& get_vertex_1() const { return vertex1; }
    flxVertex& get_vertex_2() const { return vertex2; }
    const tdouble calc_length() const { return vertex1.get_point().get_d(vertex2.get_point());  }
    void update_globalConst(const tdouble& lx) const { *(flxDOFe::LX)=lx; vertex1.get_point().set_global(vertex2.get_point(),lx/2.0+0.5); }
    const tdouble* get_geometry() const { return meshInterval; }
    const tuint get_geo_Ninter() const { return meshInterval_size; }
    const bool* get_DOFnMap() const { return DOFnMap; }
    void set_DegStd(const tuint i) { if (i>DegStd) DegStd=i; }
    void set_DegCorr(const tuint i) { if (i>DegCorr) DegCorr=i; }
    void set_KLmesh(const tuint i, const tuint o) { if (i>KLmesh_i) KLmesh_i=i; if (o>KLmesh_o) KLmesh_o=o; }
    /**
    * @brief returns 0,1,2 if the corresponding axis is parallel to the edge - 4 otherwise;
    */
    const tuint get_parallel_axis() const;
    /**
    * @brief finds the requested DOF; if do_create: create if DOF does not exist - otherwise throw an error (if 'throwError') - otherwise return NULL
    */
    flxDOFe* find_dof(const FlxDof::dof dofDir, const bool do_create, const bool throwError=true);
    const tuint get_max_pOrder() const;
    /**
    * @brief returns a reference to the pointer of the element-list. Required for inserting elements
    */
    flxElement_1D_Ptr& get_ElPointer();
    /**
    * @brief inserts an edge load
    */
    void insert_Eload(const FlxDof::dof dofDir, const bool global, FlxFunDeg* value, FlxFunction* pos1, FlxFunction* pos2, const FlxLoadCase& lc);
    /**
    * @brief assembles the (displacement) transformation matrix
    * @param mtxptr a pointer to an array of size 9 !!!
    * @param force true: displacement; false: rotation
    */
    void get_transformationMatrix(tdouble* mtxptr, const bool force=true, const bool transpose=false) const;
    /**
    * @brief add a value to the local edge stiffness matrix
    * @param elNumb the number of the sub-element (numering starts with 0)
    * @param dofe the edge-DOF under consideration
    * @param dofID ID of the DOF within the sub-element -> 0:NODE1; 1:NODE2; 2...PORD+1:INTERNAL
    */
    void update_local_StfMtx(const tuint elNumb, const flxDOFe* const dofe1, const tuint dofID1, const flxDOFe* const dofe2, const tuint dofID2, const tdouble &value);
    void update_local_StfMtx(const tuint elNumb, const flxDOFe* const dofe1, const tuint dofID1, const flxDOFe* const dofe2, const tuint dofID2, const tdouble *value, const tuint N);
    /**
    * @brief add a value to the local edge force vector
    * @param elNumb the number of the sub-element (numering starts with 0)
    * @param dofe the edge-DOF under consideration
    * @param dofID ID of the DOF within the sub-element -> 0:NODE1; 1:NODE2; 2...PORD+1:INTERNAL
    */
    void update_local_ForceVec(const tuint elNumb, const flxDOFe* const dofe, const tuint dofID, const tdouble &value);
    void update_local_ForceVec(const tuint elNumb, const flxDOFe* const dofe, const tuint dofID, const tdouble *value, const tuint N);
    /**
    * @brief returns the displacement at 'lx' in (local) direction 'dofDir'
    * @param derivative true: calc derivative of dw/dx instead of of w
    */
    const tdouble get_displacement(const FlxDof::dof dofDir, const tdouble& lx, bool derivative=false);
    /**
    * @brief create a KL-element (1D)
    */
    flxElement_KL_base* create_KL_element(FlxKLelementProp_1D* KLelProp);
    /**
    * @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
    */
    const bool map2local(const flxPoint& p) const;
    /**
    * @brief local L2-projection on the edge
    */
    void KL_integ_1(flxVec &tvec_e, const tdouble cp1, const tdouble cp2, const tuint N, FlxFunction* sD, FlxFunction* rho, const tdouble sD1) const;
    void KL_integ_2(tdouble* tvec_e, const tdouble* ni, const flxVec& e1, const flxVec& e2, const flxVec& e3, const flxVec& e4, FlxFunction* sD, FlxFunction* rho, const flxEdge& edg2) const;
    
    friend class flxEdgeList;
};
typedef flxEdge* flxEdgePtr;


class FlxRFBox;
/**
* @brief contains the edges of a FEM problem
*/
class FLXFEM_EXPORT flxEdgeList {
  private:
    /**
    * @brief true, if the topology is locked
    */
    bool locked;
    std::map<tuint, flxEdge*> box;
    tuint tmpVec_size;
    
    static flxVertexList* vl;
    #if FLX_DEBUG
    static int instance;
    #endif
    static FlxRFBox *RFbox;
    
    void refineMesh();
    void countDOF(tnlong& DofA, tnlong& FixA, tnlong& preDispA, tnlong& DofSum);
    void assembleDOF(tnlong& DofIndexFree, tnlong& FixPreIndex, tnlong& FixZeroIndex);
    void assembleStf(flxStiffnessMtxBase& stfmtx);
    void assembleForces(flxStiffnessMtxBase& stfmtx);
    void updateDisplVec(const flxStiffnessMtxBase& stfmtx);
  public:
    flxEdgeList();
    ~flxEdgeList();
    const bool is_locked() { return locked; }
    
    /**
    * @brief insert a edge into the list
    */
    void insert (const tuint index, flxEdge* edge, const bool errSerious=true);
    /**
    * @brief finds the edge with index 'index' -> if edge does not exist, an error is thrown if 'throwErr'
    */
    flxEdge* get(const tuint index, const bool throwErr,const bool errSerious=true) const;
    /**
    * @brief returns the index of the corresponding edge
    */
    const tuint get_index(const flxEdge* const edge) const;
    /**
    * @brief finds the edge with the corresponding DOF ... otherwise return NULL
    */
    flxEdge* find(const flxDOFe* DOF, const bool throwErr) const;
    /**
    * @brief finds the edge with the corresponding vertices ... otherwise return NULL
    */
    flxEdge* find(flxVertex* v1, flxVertex* v2) const;
    /**
    * @brief prints the index and the nodes of the edge -> if no such edge is found, an error is thrown if 'throwErr'
    */
    void print(std::ostream& sout) const;
    
    static void set_VertexList(flxVertexList* vlV) { vl = vlV; }
    static void set_RFbox(FlxRFBox* rfb) { RFbox = rfb; }
    friend class flxStiffnessMtxBase;
};


class flxFace;
class flxFaceList;
class flxDOFf;
typedef flxDOFf* flxDOFfPtr;
/**
* @brief DOF for a face
*/
class FLXFEM_EXPORT flxDOFf : protected FlxBoxBaseR {
  private: 
    flxDOFf* next;
    const FlxDof::dof dofDir;
    FlxDof::dof_fixed_state state;
    bool internalDOF;			// if 'true', this DOFs are local to this face (linked boxes, linked faces cannot be internal)
    bool tensorSpace;			// true: use full tensor space
    tuint DOFpOrderLx;			// polynomial order in Lx
    tuint DOFpOrderLy;			// polynomial order in Ly
    tuint *EdgeXPOrderY;		// polynomial order of the ith x-edge in y [NinterY+1]
    tuint *EdgeYPOrderX;		// polynomial order of the ith y-edge in x [NinterX+1]
    tuint NbubblesPsubEl;		// number of bubble-DOFs per sub-element
    tuint NinterSum;			// sum of all puerely internal DOFs
    tuint DOFindex_internal[4];		// starting DOF (internal) of the internal DOFs of this face
    tuint DOFindex_internal_ow[5];	// DOFs of this face wich are owned by other elements (starting DOF internal)
    tnlong DOFindex;			// this value may only be changed by 'assembleDOF'
    void* state_valueD;
    flxDOFn* dofn_link[4];		// links to the nodal DOFs of this face DOF
    flxDOFe* dofe_link[4];		// links to the edge DOFs of this face DOF
    const flxFace* its_face;			// the face of the DOF
    
    static tdouble *LX;
    
    /**
    * @brief finds the position of the element within the linked list
    * @return link to the next element
    */
    flxDOFf* link2list(flxDOFf* el2link);
    /**
    * @brief finds a certain DOF
    * @return NULL if DOF is not found
    */
    flxDOFf* find_dof(const FlxDof::dof dofDirV);
    /**
    * @brief counts how many DOFs exist
    * @param DofA Number of DOFs which are not fixed
    * @param FixA Number of DOFs which are fixed
    * @param preDisA Number of DOFs which are fixed and have a predescribed displacement
    * @param DofSum Number of DOFs (fixed and not fixed)
    * @param NinterLXY number of intervals on the face in Lx or Ly
    */
    void countDOF(tnlong& DofA, tnlong& FixA, tnlong& preDispA, tnlong& DofSum, tuint& i_DofA, tuint& LinkDofA, tuint& eown_count, const bool condenseDOF, const flxFaceList& fcl, const flxFace& currentFace);
    /**
    * @brief assembles all DOFs -> assign an index to every DOF
    * @param DofIndex the current index of all free DOFs
    * @param FixPreIndex the current index of all fixed DOFs with a nonzero predisplacement
    * @param FixZeroIndex the current index of all DOFs fixed DOFs with zero prediplacement
    */
    void assembleDOF(tnlong& DofIndexFree, tnlong& FixPreIndex, tnlong& FixZeroIndex, const bool condenseDOF, tuint& iDOF_ext, tuint& iDOF_internal);
    void assembleStf();
    /**
    * @brief returns the global DOF-index of
    * @param ID 0-3: 1st-4th node; 4-7: 1st-4th edge; 8: purely internal DOFs
    * @param length length of successive DOFs
    */
    const tnlong get_globalIndex(const int ID, tuint& length) const;
    const bool get_globalOrientation(const int ID) const;
    const tdouble get_displacement(const tdouble& lx, const tdouble& ly, const tdouble* dV) const;
      const tdouble get_displacement_help(const tdouble* r1p, const tdouble r2, const tdouble* dV, const tuint order, const bool orient) const;
    const tuint get_max_pOrder() const;
  public:
    flxDOFf(const FlxDof::dof dofDir, const FlxDof::dof_fixed_state state, const bool tensorV, const tuint DOFpOrderLxV, const tuint DOFpOrderLyV, FlxFunDeg* state_valueDV, flxDOFfPtr& start, const flxFaceList& fcl);
    ~flxDOFf();
    const tnlong get_DOFindex() const { return DOFindex; }
    const FlxDof::dof get_dofDir() const { return dofDir; }
    const tuint get_DOFpOrderLx() const { return DOFpOrderLx; }
    const tuint get_DOFpOrderLy() const { return DOFpOrderLy; }
    
    const tuint get_NbubblesPsubEl() const { return NbubblesPsubEl; }
    const tuint get_EdgeXPOrderY(const tuint i) const { return EdgeXPOrderY[i]; }
    const tuint get_EdgeYPOrderX(const tuint i) const { return EdgeYPOrderX[i]; }
    const bool is_tensorSpace() const { return tensorSpace; }
    const tuint get_localIndex(const tuint elIDx, const tuint elIDy, const int ID) const;
    
    /**
    * @brief determines which nodal DOFs are required to link the face
    * @param Tmtx transformation matrix of the face (array of size 9)
    * @param reqMap bool-array (set to false): true->nodal DOF is required; (array of size 3)
    */
    void requires_nodal_DOF(const tdouble* Tmtx, bool* reqMap) const;
    
    static void set_Const(FlxConstantBox& ConstantBox);
    friend class flxFace;
};


/**
* @brief returns the common vertex of two edges
* @return true: e1.vertex1 is common vertex; false: e1.vertex2 is common vertex
*/
flxVertex* get_common_vtx(const flxEdge& e1, const flxEdge& e2);
    

class flxElement_2D_base;
typedef flxElement_2D_base* flxElement_2D_Ptr;
class FlxKLelementProp_2D;
/**
* @brief a FEM-face
* Structure of the local face stiffness matrix & Co.
* NODAL_PART(4x3) EDGE_1_PART...EDGE_4_PART(2..pe) INTERNAL_PART
*/
class FLXFEM_EXPORT flxFace : public flxDomain_base {
  private:
    flxVertex* VtxLst[4];
    flxEdge* EdgLst[4];
    flxDOFf* dofList;
    flxElement_2D_base* elList;
//     flxFLoad* loadList;
    tdouble detJ_const;
    bool is_detJ_const;
    bool isAligned;			// edges parallel to the global coordinate system
    bool hfemProb;			// true: h-fem elements may use this face; false: not ...
    bool edgOrient[4];			// true, if the edge is oriented according to the local coordinate system 
    const bool condenseDOF;		// true: internal DOFs are condensed
    const bool default_tensor;			// true: tensor space is used - otherwise trunk space
    tuint default_order;		// default order of the internal modes
    tuint DOFf_start;			// DOFs which are owned by other elements (their number)
    tuint DOFf_external;		// number of external face-DOFs
    tuint DOFf_internal;		// number of internal face-DOFs (can only be bubble-modes)
    FlxDof::dir mapL2G[15];		// determines to which DOF a local DOF maps (only for aligned elements)  nodes,edge1,edge2,edge3,edge4
      bool mapL2G_Orient[15];		// true (both axes point in the same direction) or false (different directions)
    tdouble Jaco_part2[6];		// (3x2)-matrix (for use with get_Jaco_part1)
    tdouble transMtxIP[9];		// (3x3)-transformation matrix from the in_plane_system to global coordinates
    flxPoint gVec[4];			// g(0):Aufhängepunkt; g(1)=N1-N0; g(2)=N3-N0; g(3):orthogonal to 1,2(normalized)
    tVec* fStfMtx;			// stiffness matrix of the edge -> NULL if no internal DOFs exist (symmetric matrix)
    tVec* fForceVec;			// force vector of the edge -> NULL if no internal DOFs exist
    tVec* fDisplVec;			// displacement vector of the edge -> NULL if elList=NULL
    const tuint mesh_min;			// min. depth of the refinement-tree
    const tuint mesh_max;			// max. depth of the refinement-tree
    tuint meshIntLx;			// number of intervals in xi-direction
    tuint meshIntLy;			// number of intervals in nu-direction
    const tdouble* meshXLx;		// coordinates of each interval in xi-direction
    const tdouble* meshYLy;		// coordinates of each interval in nu-direction
    tuint **mesh;			// mesh of each sub-element
    tdouble **mesh_cc;			// center_x,center_y,area
    FlxFunction* geo_fun;			// describes the geometry
    FlxFunction* cellAreaPoints;		// describes the number of points in a direction to obtain the cell area of cut cells
    tuint DegStd;			// KL-integration: degree standard deviation function
    tuint DegCorr;			// KL-integration: degree correlation coefficient function
    static flxFaceList* fcl;
    static flxFEM* FEM;
    
    void check_hfem();
    void refineMesh();
      void refineMesh_help(std::vector<tuint> &vec, std::vector<tdouble> &vec_cc, const tuint level, const tdouble xm, const tdouble ym, const tdouble dxh, const tdouble dyh, const tdouble lxm, const tdouble lym);
      const tdouble get_cellarea(const tdouble dh, const tdouble dxh, const tdouble dyh, const tdouble xm, const tdouble ym, tdouble& cmx, tdouble& cmy);
    void countDOF(tnlong& DofA, tnlong& FixA, tnlong& preDispA, tnlong& DofSum, flxEdgeList &vl, tuint& tmpVec_size);
    void assembleDOF(tnlong& DofIndexFree, tnlong& FixPreIndex, tnlong& FixZeroIndex);
    void assembleStf(flxStiffnessMtxBase& stfmtx, tVec* tmpVec);
    void assembleForces(flxStiffnessMtxBase& stfmtx, tVec* tmpVec);
    void updateDisplVec(const flxStiffnessMtxBase& stfmtx);
  public:
    flxFace(flxEdge* e1, flxEdge* e2, flxEdge* e3, flxEdge* e4, flxDOFf* dofList, const tuint default_order, const bool default_tensor, const bool condenseDOF, FlxFunction* geo_fun, FlxFunction* cellAreaPoints, const tuint mesh_min, const tuint mesh_max);
    ~flxFace();
    const tdouble calc_area(const bool distorted=false) const { return get_detJ(ZERO,ZERO)*4; }
    void update_globalConst(const tdouble& lx, const tdouble& ly) const;
//     const tuint get_geo_Nlx() const { return meshIntLx; }
//     const tuint get_geo_Nly() const { return meshIntLy; }
    /**
    * @brief determines if all the edges of the face are parallel to the axis of the coordinate system
    */
    void check_OrthPar() const;
    /**
    * @brief returns the ith vertex of the element i \in [0;3]
    */
    flxVertex& get_vertex(const tuint i) const { return *(VtxLst[i]); }
    flxEdge& get_edge(const tuint i) const { return *(EdgLst[i]); }
    const bool get_edge_Orient(const tuint i) const { return edgOrient[i]; }
    const FlxDof::dir get_mapL2G(const tuint i) const { return mapL2G[i]; }
    const bool get_mapL2G_Orient(const tuint i) const { return mapL2G_Orient[i]; }
    /**
    * @brief calculates determinant of the jacobian (of the in_plan_system)
    */
    const tdouble get_detJ(const tdouble& lx, const tdouble& ly) const { return is_detJ_const?detJ_const:((Jaco_part2[0]*Jaco_part2[3]*(1+lx)+(1+ly)*(Jaco_part2[2]*Jaco_part2[5]-Jaco_part2[3]*Jaco_part2[4])-Jaco_part2[0]*Jaco_part2[0]*(lx+ly))/8.0); }
    const tdouble get_detJ_const() const { return detJ_const; }
    const bool get_is_detJ_const() const { return is_detJ_const; }
    /**
    * @brief calculates J[2x2-matrix]
    */
    void get_J(tdouble* J, const tdouble& lx, const tdouble& ly) const;
    const tdouble get_bovara(const tuint id1, const tuint id2) const;
    const tdouble* get_TransMtx() const { return transMtxIP; }
    void set_DegStd(const tuint i);
    void set_DegCorr(const tuint i);
    /**
    * @brief finds the requested DOF; if do_create: create if DOF does not exist - otherwise throw an error (if 'throwError') - otherwise return NULL
    */
    flxDOFf* find_dof(const FlxDof::dof dofDir, const bool do_create, const bool throwError=true);
    /**
    * @brief returns a reference to the pointer of the element-list. Required for inserting elements
    */
    flxElement_2D_Ptr& get_ElPointer();
    const flxDOFf* const get_DOFlist() const { return dofList; }
    /**
    * @brief inserts a face load
    */
//     void insert_Eload(const FlxDof::dof dofDir, const bool global, FlxFunDeg* value, FlxFunction* pos1, FlxFunction* pos2, const FlxLoadCase& lc);
    /**
    * @brief add a value to the local edge stiffness matrix
    * @param elNumb the number of the sub-element (numering starts with 0)
    * @param dofe the edge-DOF under consideration
    * @param dofID ID of the DOF within the sub-element -> 0:NODE1; 1:NODE2; 2...PORD+1:INTERNAL
    */
    void update_local_StfMtx(const tuint id1, const tuint id2, const tdouble *value, const tuint N);
    /**
    * @brief add a value to the local edge force vector
    * @param elNumb the number of the sub-element (numering starts with 0)
    * @param dofe the edge-DOF under consideration
    * @param dofID ID of the DOF within the sub-element -> 0:NODE1; 1:NODE2; 2...PORD+1:INTERNAL
    */
    void update_local_ForceVec(const tuint elNumb, const flxDOFe* const dofe, const tuint dofID, const tdouble &value);
    void update_local_ForceVec(const tuint elNumb, const flxDOFe* const dofe, const tuint dofID, const tdouble *value, const tuint N);
    /**
    * @brief returns the displacement at 'lx,ly' in (local) direction 'dofDir'
    * @param derivative true: calc derivative of dw/dx instead of of w
    * TODO does this make sense for this type of element ???
    */
    const tdouble get_displacement(const FlxDof::dof dofDir, const tdouble& lx, const tdouble& ly);
    flxEdge** get_EdgLst() { return EdgLst; }
    const tdouble get_geofun_calc() const { return geo_fun?(geo_fun->calc()):ONE; }
    /**
    * @brief create a KL-element (2D)
    */
    flxElement_KL_base* create_KL_element(FlxKLelementProp_2D* KLelProp);
    /**
    * @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
    */
    const bool map2local(const flxPoint& p) const;
    
    static void set_FEM(flxFEM* FEMv);
    
    friend class flxDOFf;
    friend class flxFaceList;
};

/**
* @brief contains the edges of a FEM problem
*/
class FLXFEM_EXPORT flxFaceList {
  private:
    /**
    * @brief true, if the topology is locked
    */
    bool locked;
    std::map<tuint, flxFace*> box;
    tuint tmpVec_size;
    
    static flxEdgeList* el;
    #if FLX_DEBUG
    static int instance;
    #endif
    static FlxRFBox *RFbox;
    
    void refineMesh();
    void countDOF(tnlong& DofA, tnlong& FixA, tnlong& preDispA, tnlong& DofSum);
    void assembleDOF(tnlong& DofIndexFree, tnlong& FixPreIndex, tnlong& FixZeroIndex);
    void assembleStf(flxStiffnessMtxBase& stfmtx);
    void assembleForces(flxStiffnessMtxBase& stfmtx);
    void updateDisplVec(const flxStiffnessMtxBase& stfmtx);
  public:
    flxFaceList();
    ~flxFaceList();
    const bool is_locked() { return locked; }
    
    /**
    * @brief insert a face into the list
    */
    void insert (const tuint index, flxFace* face, const bool errSerious=true);
    /**
    * @brief finds the face with index 'index' -> if edge does not exist, an error is thrown if 'throwErr'
    */
    flxFace* get(const tuint index, const bool throwErr,const bool errSerious=true) const;
    /**
    * @brief finds the face which contains doff -> if no such face is found, an error is thrown if 'throwErr'
    */
    flxFace* get(const flxDOFf* const doff) const;
    /**
    * @brief returns the index of the corresponding face
    */
    const tuint get_index(const flxFace* const face) const;
    /**
    * @brief finds the face with the corresponding DOF ... otherwise return NULL
    */
    flxFace* find(const flxDOFf* DOF, const bool throwErr) const;
    /**
    * @brief prints the index and the edges of the face
    */
    void print(std::ostream& sout) const;
    
    static void set_EdgeList(flxEdgeList* elV) { el = elV; }
    static void set_RFbox(FlxRFBox* rfb) { RFbox = rfb; }
    friend class flxStiffnessMtxBase;
};



struct smptid {
  tuint dir;	// 0,1,2
  tdouble pomh;	// +/- dxh
};

class FlxKLelementProp_3D;
/**
* @brief a FEM-box
*/
class FLXFEM_EXPORT flxBox : public flxDomain_base {
  private:
    flxVertex* VtxLst[8];
    flxEdge* EdgLst[12];
    flxFace* FacLst[6];
    tdouble detJ_const;
    tdouble xm,ym,zm;		// middle of the box (in global coordinates)
    smptid transmtx[3];		// maps local to global
    tuint DegStd;			// KL-integration: degree standard deviation function
    tuint DegCorr;			// KL-integration: degree correlation coefficient function
    
    /**
    * @brief returns the common vertex of two edges
    * @return index of the ith edge of face 1
    */
    flxEdge* get_common_edg(const flxFace& f1, const flxFace& f2) const;

  public:
    flxBox(flxFace* f0, flxFace* f1, flxFace* f2, flxFace* f3, flxFace* f4, flxFace* f5);
    ~flxBox();
    const tdouble calc_volume() const { return detJ_const*8; }
    void update_globalConst(flxPoint& lxp) const;
    flxFace* const* get_FacLst() const { return FacLst; }
    /**
    * @brief calculates determinant of the jacobian (of the in_plan_system)
    */
    const tdouble get_detJ() const { return detJ_const; }
    void set_DegStd(const tuint i);
    void set_DegCorr(const tuint i);
    
    /**
    * @brief create a KL-element (3D)
    */
    flxElement_KL_base* create_KL_element(FlxKLelementProp_3D* KLelProp);
    /**
    * @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
    */
    const bool map2local(const flxPoint& p) const;
    
    static void set_Const(FlxConstantBox& ConstantBox);
};

/**
* @brief contains the edges of a FEM problem
*/
class FLXFEM_EXPORT flxBoxList {
  private:
    /**
    * @brief true, if the topology is locked
    */
    bool locked;
    std::map<tuint, flxBox*> box;
    
    static flxFaceList* fl;
    #if FLX_DEBUG
    static int instance;
    #endif
    static FlxRFBox *RFbox;
    
    void refineMesh();
    void countDOF(tnlong& DofA, tnlong& FixA, tnlong& preDispA, tnlong& DofSum);
    void assembleDOF(tnlong& DofIndexFree, tnlong& FixPreIndex, tnlong& FixZeroIndex);
    void assembleStf(flxStiffnessMtxBase& stfmtx);
    void assembleForces(flxStiffnessMtxBase& stfmtx);
    void updateDisplVec(const flxStiffnessMtxBase& stfmtx);
  public:
    flxBoxList();
    ~flxBoxList();
    const bool is_locked() { return locked; }
    
    /**
    * @brief insert a box into the list
    */
    void insert (const tuint index, flxBox* boxV, const bool errSerious=true);
    /**
    * @brief finds the box with index 'index' -> if box does not exist, an error is thrown if 'throwErr'
    */
    flxBox* get(const tuint index, const bool throwErr,const bool errSerious=true) const;
    /**
    * @brief returns the index of the corresponding box
    */
    const tuint get_index(const flxBox* const boxV) const;
    /**
    * @brief prints the index and the faces of the box
    */
    void print(std::ostream& sout) const;
    
    static void set_FaceList(flxFaceList* flV) { fl = flV; }
    static void set_RFbox(FlxRFBox* rfb) { RFbox = rfb; }
    friend class flxStiffnessMtxBase;
};



#endif // fesslix_flxfem_geometry_H

