#ifndef DUNE_MICROSTRUCTURE_PRESTRAINEDMATERIAL_HH
#define DUNE_MICROSTRUCTURE_PRESTRAINEDMATERIAL_HH

#include <dune/grid/uggrid.hh>
#include <dune/grid/yaspgrid.hh>
#include <dune/functions/gridfunctions/gridviewfunction.hh>
#include <dune/functions/gridfunctions/gridviewentityset.hh>
#include <dune/common/parametertree.hh>
#include <dune/fufem/dunepython.hh>
#include <dune/microstructure/matrix_operations.hh>

// #include <dune/microstructure/materialDefinitions.hh> //deprecated


using namespace MatrixOperations;
using namespace Dune::Functions;
using namespace Dune::Functions::BasisFactory;
using std::pow;
using std::abs;
using std::sqrt;
using std::sin;
using std::cos;

using std::shared_ptr;
using std::make_shared;






//   // ----------------------------------------------------------------------------------
//   template<class Domain>
//   int indicatorFunction_material(const Domain& x)
//   {
//     double theta=0.25;
//     if (x[0] <(-0.5+theta) and x[2]<(-0.5+theta))
//         return 1;    //#Phase1
//     else if (x[1]<(-0.5+theta) and x[2]>(0.5-theta))
//         return 2;    //#Phase2
//     else 
//         return 3;    //#Phase3
//   }
//  MatrixRT getL(const int& phase)
//   {
//       const FieldVector<double,3> mu     = {80.0, 80.0, 60.0};
//       const FieldVector<double,3> lambda = {80.0, 80.0, 25.0};



//       if (phase == 1)
//         return 2.0 * mu[0] * sym(G) + lambda[0] * trace(sym(G)) * Id(); //#Phase1 //Isotrop(mu,lambda)
//       else if (phase == 2)
//         return 2.0 * mu[1] * sym(G) + lambda[1] * trace(sym(G)) * Id(); //#Phase2
//       else 
//         return 2.0 * mu[2] * sym(G) + lambda[2] * trace(sym(G)) * Id(); //#Phase3
//   }
//   MatrixRT prestrain_material(const int& phase)
//   {
//       if (phase == 1)
//         return {{1.0, 0.0, 0.0},    //#Phase1
//                 {0.0, 1.0, 0.0},
//                 {0.0, 0.0, 1.0}
//                };
//       else if (phase == 2)
//         return {{1.0, 0.0, 0.0},    //#Phase2
//                 {0.0, 1.0, 0.0},
//                 {0.0, 0.0, 1.0}
//                };
//       else 
//         return {{0.0, 0.0, 0.0},    //#Phase3
//                 {0.0, 0.0, 0.0},
//                 {0.0, 0.0, 0.0}
//                };
//   }


//--------------------------------------------------------
template <class GridView>         
class prestrainedMaterial
{

public:
  static const int dimworld = 3;  //GridView::dimensionworld;
  static const int dim = 3;       //Domain::dimension;
  using Domain = typename GridView::template Codim<0>::Geometry::GlobalCoordinate;
  using LocalDomain = typename GridView::template Codim<0>::Geometry::LocalCoordinate;
  using Element = typename GridViewEntitySet<GridView, 0>::Element;
  using ScalarRT = Dune::FieldVector< double, 1>;
  using VectorRT = Dune::FieldVector< double, dimworld>;
  using MatrixRT = Dune::FieldMatrix< double, dimworld, dimworld>;
  using FuncScalar = std::function< double(const Domain&) >;
  using Func2int = std::function< int(const Domain&) >;
  using Func2Tensor = std::function< MatrixRT(const Domain&) >;
  using Func2TensorParam = std::function< MatrixRT(const MatrixRT& ,const Domain&) >;
  using Func2TensorPhase = std::function< MatrixRT(const MatrixRT& ,const int&) >;
  using MatrixFunc  = std::function< MatrixRT(const MatrixRT&) >;
  using MatrixPhase = std::function< MatrixRT(const int&) >;

  // using VoigtMatrix = FieldMatrix< double, 6, 6>;

protected:

  const GridView& gridView_;
  const Dune::ParameterTree& parameterSet_;
  std::string materialFunctionName_;

  // MatrixFunc L1_;
  // MatrixFunc L2_;
  // MatrixFunc L3_;

  // Elasticity tensors (in voigt notation)
  Dune::FieldMatrix<double,6,6> L1_;
  Dune::FieldMatrix<double,6,6> L2_;
  Dune::FieldMatrix<double,6,6> L3_;
  Dune::FieldMatrix<double,6,6> L4_; //not used yet

  Func2Tensor prestrain1_;
  Func2Tensor prestrain2_; 
  Func2Tensor prestrain3_;  
  Func2Tensor prestrain4_;  //not used yet

  Python::Module module_;

  // const FuncScalar mu_; 
  // const FuncScalar lambda_; 
  // const int phases_;
  // Func2Tensor elasticityTensor_;
  // Func2TensorParam elasticityTensor_;
  Func2TensorPhase elasticityTensor_;
  MatrixPhase prestrain_;


  Func2int indicatorFunction_;
  GridViewFunction<int(const Domain&), GridView> indicatorFunctionGVF_;
  LocalFunction<int(const Domain&), typename GridViewEntitySet<GridView, 0>::Element >  localIndicatorFunction_;


  //Transformation matrix for Voigt notation
  Dune::FieldMatrix<double,6,6> D_ = {{1.0, 0.0, 0.0, 0.0 , 0.0, 0.0},
                              {0.0, 1.0, 0.0, 0.0 , 0.0, 0.0},
                              {0.0, 0.0, 1.0, 0.0 , 0.0, 0.0},
                              {0.0, 0.0, 0.0, sqrt(2.0) , 0.0, 0.0},
                              {0.0, 0.0, 0.0, 0.0 , sqrt(2.0), 0.0},
                              {0.0, 0.0, 0.0, 0.0 , 0.0, sqrt(2.0)}
                            };
  Dune::FieldMatrix<double,6,6> D_inv = {{1.0, 0.0, 0.0, 0.0 , 0.0, 0.0},
                                  {0.0, 1.0, 0.0, 0.0 , 0.0, 0.0},
                                  {0.0, 0.0, 1.0, 0.0 , 0.0, 0.0},
                                  {0.0, 0.0, 0.0, 1.0/sqrt(2.0) , 0.0, 0.0},
                                  {0.0, 0.0, 0.0, 0.0 , 1.0/sqrt(2.0), 0.0},
                                  {0.0, 0.0, 0.0, 0.0 , 0.0, 1.0/sqrt(2.0)}
                                };



public: 
    ///////////////////////////////
    // constructor
    ///////////////////////////////
    prestrainedMaterial(const GridView gridView,
                        const Dune::ParameterTree& parameterSet)
                       : gridView_(gridView), 
                         parameterSet_(parameterSet)
    {
	  std::string materialFunctionName_ = parameterSet.get<std::string>("materialFunction", "material_1");
    std::cout << "materialFunctionName_ : " << materialFunctionName_ << std::endl;

    // setupMaterial(materialFunctionName_);  //old-Version

    module_ = Python::import(materialFunctionName_);  //TODO use this instead?



    // const FieldVector<double,3> mu     = {80.0, 80.0, 60.0};
    // const FieldVector<double,3> lambda = {80.0, 80.0, 25.0};


    //elastic moduli in the order (E1,E2,E3,G12,G23,G31,nu12,nu13,nu23)

    //-- Walnut see [Dimwoodie; Timber its nature and behavior p.109]
    // const FieldVector<double,9> walnut_parameters={11.2e3,630,1190,700,230,960,0.63 ,0.49,0.37};

    //-- Norway spruce see [Dimwoodie; Timber its nature and behavior p.109]
    // const FieldVector<double,9> Nspruce_parameters={10.7e3,430,710,620,23,500, 0.51 ,0.38,0.31};

    //frame vectors
    VectorRT e1 = {1.0,0.0,0.0};
    VectorRT e2 = {0.0,1.0,0.0};
    VectorRT e3 = {0.0,0.0,1.0};



    // L1_ = orthotropic(e1,e2,e3,walnut_parameters);
    // L2_ = orthotropic(e1,e2,e3,Nspruce_parameters);
    // L3_ = orthotropic(e1,e2,e3,Nspruce_parameters);

    setup(materialFunctionName_);
    // setup("material");


    indicatorFunctionGVF_ = Dune::Functions::makeGridViewFunction(indicatorFunction_, gridView_);
    localIndicatorFunction_ = localFunction(indicatorFunctionGVF_);


    // L1_ = transversely_isotropic(e1,e2,e3, {11.2e3,1190,960,0.63 ,0.37});
    // L2_ = transversely_isotropic(e1,e2,e3, {11.2e3,1190,960,0.63, 0.37});
    // L3_ = transversely_isotropic(e1,e2,e3, {10.7e3,710 ,500,0.51 ,0.31});

    // L1_ = isotropic(mu[0],lambda[0]);
    // L2_ = isotropic(mu[1],lambda[1]);
    // L3_ = isotropic(mu[2],lambda[2]); 




    // L1_ = {{lambda[0]+2.0*mu[0], lambda[0], lambda[0], 0.0 , 0.0, 0.0},
    //         {lambda[0], lambda[0]+2*mu[0], lambda[0], 0.0 ,0.0 ,0.0},
    //         {lambda[0], lambda[0],  lambda[0]+2.0*mu[0], 0.0, 0.0, 0.0},
    //         { 0.0 ,0.0, 0.0, 2.0*mu[0], 0.0, 0.0},
    //         { 0.0 ,0.0, 0.0, 0.0, 2.0*mu[0], 0.0},
    //         { 0.0 ,0.0, 0.0, 0.0, 0.0, 2.0*mu[0]}
    //       };

    // L2_ = {{lambda[1]+2.0*mu[1], lambda[1], lambda[1], 0.0 , 0.0, 0.0},
    //         {lambda[1], lambda[1]+2*mu[1], lambda[1], 0.0 ,0.0 ,0.0},
    //         {lambda[1], lambda[1],  lambda[1]+2.0*mu[1], 0.0, 0.0, 0.0},
    //         { 0.0 ,0.0, 0.0, 2.0*mu[1], 0.0, 0.0},
    //         { 0.0 ,0.0, 0.0, 0.0, 2.0*mu[1], 0.0},
    //         { 0.0 ,0.0, 0.0, 0.0, 0.0, 2.0*mu[1]}
    //       };

    // L3_ = {{lambda[2]+2.0*mu[2], lambda[2], lambda[2], 0.0 , 0.0, 0.0},
    //       {lambda[2], lambda[2]+2.0*mu[2], lambda[2], 0.0 ,0.0 ,0.0},
    //       {lambda[2], lambda[2],  lambda[2]+2.0*mu[2], 0.0, 0.0, 0.0},
    //       { 0.0 ,0.0, 0.0, 2.0*mu[2], 0.0, 0.0},
    //       { 0.0 ,0.0, 0.0, 0.0, 2.0*mu[2], 0.0},
    //       { 0.0 ,0.0, 0.0, 0.0, 0.0, 2.0*mu[2]}
    //     };

    // printmatrix(std::cout, D_, "D_", "--");
    // printmatrix(std::cout, D_inv, "D_inv", "--");
    // printmatrix(std::cout, L1_, "L1_", "--");
    // L1_.leftmultiply(D_inv);
    // printmatrix(std::cout, L1_, "L1_.leftmultiply(D_inv)", "--");
    // L1_.rightmultiply(D_);
    // printmatrix(std::cout, L1_, "L1_.rightmultiply(D_)", "--");
    

    // Python::Module module = Python::import(materialFunctionName_);
    // elasticityTensor_ = Python::make_function<MatrixRT>(module.get("L"));
    // elasticityTensor_ = setupElasticityTensor(materialFunctionName_);
    // auto indicatorFunction = Python::make_function<int>(module.get("indicatorFunction"));
    // indicatorFunction_ = Python::make_function<int>(module.get("indicatorFunction"));
    // indicatorFunction_ =  Dune::Functions::makeGridViewFunction(indicatorFunction , gridView_);
    // module.get("Phases").toC<int>(Phases_);
    // L1_ = Python::make_function<MatrixRT>(module.get("L1"));
    // L2_ = Python::make_function<MatrixRT>(module.get("L2"));
    // L3_ = Python::make_function<MatrixRT>(module.get("L3"));
    // bool isotropic_ = true; // read from module File 
    // auto Test = Dune::Functions::makeGridViewFunction(MAT<Domain> , gridView_); //not working 
    } 





Dune::FieldMatrix<double,6,6> setupPhase(const std::string phaseType, Python::Module module, int phase)
{
  std::string materialParameters_string;
  std::cout << "Setup phase "<< phase << " as " << phaseType << " material." << std::endl;

  switch (phase)
  {
    case 1:
    {
      materialParameters_string = "materialParameters_phase1";
      break;
    }
    case 2:
    {
      materialParameters_string = "materialParameters_phase2";
      break;
    }
    case 3:
    {
      materialParameters_string= "materialParameters_phase3";
      break;
    }
    default:
    DUNE_THROW(Dune::Exception, "Phase number not implemented.");
        break;
  }

  if(phaseType == "isotropic")                           //TODO SetupPHASE (phase_type)
  {
      Dune::FieldVector<double,2> materialParameters(0);
      module.get(materialParameters_string).toC<Dune::FieldVector<double,2>>(materialParameters);
      return isotropic(materialParameters[0],materialParameters[1]);
  }
  if(phaseType == "orthotropic")                           //TODO SetupPHASE (phase_type)
  {
      // TODO: Generalize here:
      //frame vectors
      VectorRT e1 = {1.0,0.0,0.0};
      VectorRT e2 = {0.0,1.0,0.0};
      VectorRT e3 = {0.0,0.0,1.0};
      Dune::FieldVector<double,9> materialParameters(0);
      module.get(materialParameters_string).toC<Dune::FieldVector<double,9>>(materialParameters);
      return orthotropic(e1,e2,e3,materialParameters);
  }
  if(phaseType == "transversely_isotropic")                           //TODO SetupPHASE (phase_type)
  {
      // TODO: Generalize here:
      //frame vectors
      VectorRT e1 = {1.0,0.0,0.0};
      VectorRT e2 = {0.0,1.0,0.0};
      VectorRT e3 = {0.0,0.0,1.0};
      Dune::FieldVector<double,5> materialParameters(0);
      module.get(materialParameters_string).toC<Dune::FieldVector<double,5>>(materialParameters);
      return transversely_isotropic(e1,e2,e3,materialParameters);
  }
  if(phaseType == "general_anisotropic")                           //TODO SetupPHASE (phase_type)
  {
      // TODO: Generalize here:
      //frame vectors
      VectorRT e1 = {1.0,0.0,0.0};
      VectorRT e2 = {0.0,1.0,0.0};
      VectorRT e3 = {0.0,0.0,1.0};
      Dune::FieldMatrix<double,6,6> materialParameters(0); //compliance matric
      module.get(materialParameters_string).toC<Dune::FieldMatrix<double,6,6>>(materialParameters);
      printmatrix(std::cout, materialParameters, "Compliance matrix used:", "--");
      return general_anisotropic(e1,e2,e3,materialParameters);
  }
  else
    DUNE_THROW(Dune::Exception, " Unknown material class for phase ");
}



  //---function that determines elasticity Tensor 
  // void setup(const std::string name, const int Phases)  // cant use materialFunctionName_ here!?
  void setup(const std::string name)  // cant use materialFunctionName_ here!?
  {

    Python::Module module = Python::import(name);
    indicatorFunction_ = Python::make_function<int>(module.get("indicatorFunction"));

    std::cout << "---- setup material... " << std::endl;
    
    int Phases;
    module.get("Phases").toC<int>(Phases);
    std::cout << "Number of Phases: " << Phases  << std::endl;


    switch (Phases)
    {
    case 1:
    {
      std::string phase1_type;
      module.get("phase1_type").toC<std::string>(phase1_type);
      L1_ = setupPhase(phase1_type, module,1);
      prestrain1_ = Python::make_function<MatrixRT>(module.get("prestrain_phase1"));
      break;
    }
    case 2:
    {
      std::string phase1_type;
      module.get("phase1_type").toC<std::string>(phase1_type);
      std::string phase2_type;
      module.get("phase2_type").toC<std::string>(phase2_type);

      L1_ = setupPhase(phase1_type, module,1);
      L2_ = setupPhase(phase2_type, module,2);
      prestrain1_ = Python::make_function<MatrixRT>(module.get("prestrain_phase1"));
      prestrain2_ = Python::make_function<MatrixRT>(module.get("prestrain_phase2"));

      break;
    }
    case 3:
    {
      std::string phase1_type;
      module.get("phase1_type").toC<std::string>(phase1_type);
      std::string phase2_type;
      module.get("phase2_type").toC<std::string>(phase2_type);
      std::string phase3_type;
      module.get("phase3_type").toC<std::string>(phase3_type);

      L1_ = setupPhase(phase1_type, module,1);
      L2_ = setupPhase(phase2_type, module,2);
      L3_ = setupPhase(phase3_type, module,3);

      prestrain1_ = Python::make_function<MatrixRT>(module.get("prestrain_phase1"));
      prestrain2_ = Python::make_function<MatrixRT>(module.get("prestrain_phase2"));
      prestrain3_ = Python::make_function<MatrixRT>(module.get("prestrain_phase3"));
        
      // if (phase1_type == "isotropic" )                           //TODO SetupPHASE (phase_type)
      // {
      //   FieldVector<double,2> materialParameters_phase1(0);
      //   module.get("materialParameters_phase1").toC<FieldVector<double,2>>(materialParameters_phase1);
      //   L1_ = isotropic(materialParameters_phase1[0],materialParameters_phase1[1]);
      // }
      // if (phase2_type == "isotropic" )                           //TODO SetupPHASE (phase_type)
      // {
      //   FieldVector<double,2> materialParameters_phase2(0);
      //   module.get("materialParameters_phase2").toC<FieldVector<double,2>>(materialParameters_phase2);
      //   L2_ = isotropic(materialParameters_phase2[0],materialParameters_phase2[1]);
      // }
      // if (phase3_type == "isotropic" )                           //TODO SetupPHASE (phase_type)
      // {
      //   FieldVector<double,2> materialParameters_phase3(0);
      //   module.get("materialParameters_phase3").toC<FieldVector<double,2>>(materialParameters_phase3);
      //   L3_ = isotropic(materialParameters_phase3[0],materialParameters_phase3[1]);
      // }
      break;
    }
    default:
      break;
    }
    std::cout << "Material setup done." << std::endl;
  }

  ///////////////////////////////////////////////////////////////////////
  // //---function that determines elasticity Tensor 
  //   void setupMaterial(const std::string name)  // cant use materialFunctionName_ here!?
  //   {
  //       // std::cout << "Using material definition:" << name << std::endl;
  //       if(name == "material_neukamm")
  //       {   
  //         indicatorFunction_ = indicatorFunction_material_neukamm<Domain>;
  //         elasticityTensor_ = material_neukamm;
  //         prestrain_ = prestrain_material_neukamm;
  //       }
  //       else if(name == "two_phase_material_1")
  //       {
  //         indicatorFunction_ = indicatorFunction_two_phase_material_1<Domain>;
  //         elasticityTensor_ = two_phase_material_1;
  //         prestrain_ = prestrain_two_phase_material_1;
  //       }
  //       else if(name == "two_phase_material_2")
  //       {
  //         indicatorFunction_ = indicatorFunction_two_phase_material_2<Domain>;
  //         elasticityTensor_ = two_phase_material_2;
  //         prestrain_ = prestrain_two_phase_material_2;
  //       }
  //       else if(name == "homogeneous")
  //       {
  //         indicatorFunction_ = indicatorFunction_material_homogeneous<Domain>;
  //         elasticityTensor_ = material_homogeneous;
  //         prestrain_ = prestrain_homogeneous;
  //       }
  //       else
  //        DUNE_THROW(Exception, "There exists no material with this name "); 
  //   }





  ////////////////////////////////////////////////////////
  //      MATERIAL CLASSES DEFINITIONS:
  ////////////////////////////////////////////////////////

  //--- Definition of isotropic elasticity tensor (in voigt notation)
  Dune::FieldMatrix<double,6,6> isotropic(const double& mu, const double& lambda)
  {
      return {{lambda+2.0*mu, lambda       , lambda       , 0.0   , 0.0   , 0.0   },
              {lambda       , lambda+2.0*mu, lambda       , 0.0   , 0.0   , 0.0   },
              {lambda       , lambda       , lambda+2.0*mu, 0.0   , 0.0   , 0.0   },
              {  0.0        ,  0.0         , 0.0          , 2.0*mu, 0.0   , 0.0   },
              {  0.0        ,  0.0         , 0.0          , 0.0   , 2.0*mu, 0.0   },
              {  0.0        ,  0.0         , 0.0          , 0.0   , 0.0   , 2.0*mu}
             };
  }
  //-------------------------------------------------------------------------------

  //--- Definition of orthotropic elasticity tensor (in voigt notation)
  // - Input: (Youngs modulus, shear modulus, Poisson ratio): {E1,E2,E3,G12,G23,G31,nu12,nu13,nu23}
  // - Output: compliance Matrix
  Dune::FieldMatrix<double,6,6> orthotropic(const VectorRT& framevector1,
                                      const VectorRT& framevector2,
                                      const VectorRT& framevector3,
                                      const Dune::FieldVector<double,9>& p //elastic moduli {E1,E2,E3,G12,G23,G31,nu12,nu13,nu23}
                                      ) 
  {
    // (see https://en.wikipedia.org/wiki/Orthotropic_material)
      auto E_1 = p[0];
      auto E_2 = p[1];
      auto E_3 = p[2];
      auto G_12 = p[3];
      auto G_23 = p[4];
      auto G_31 = p[5];
      auto nu_12 = p[6];
      auto nu_13 = p[7];
      auto nu_23 = p[8];
      //other three poisson ratios are not independent 
      auto nu_21 = (p[6]/p[0])*p[1];
      auto nu_31 = (p[7]/p[0])*p[2];
      auto nu_32 = (p[8]/p[1])*p[2]; 

      //compliance matrix
      Dune::FieldMatrix<double,6,6> C = {{1.0/E_1      , -nu_21/E_2   , -nu_31/E_3   , 0.0     , 0.0     , 0.0   },
                                       {-nu_12/E_1   , 1.0/E_2      , -nu_32/E_3   , 0.0     , 0.0     , 0.0   },
                                       {-nu_13/E_1   , -nu_23/E_2   , 1.0/E_3      , 0.0     , 0.0     , 0.0   },
                                       {  0.0        ,  0.0         , 0.0          , 1.0/G_23, 0.0     , 0.0   },
                                       {  0.0        ,  0.0         , 0.0          , 0.0     , 1.0/G_31, 0.0   },
                                       {  0.0        ,  0.0         , 0.0          , 0.0     , 0.0     , 1.0/G_12}
                                      };

    
      // printmatrix(std::cout, C, "C", "--");

      //--- return elasticity tensor 
      C.invert();
      // printmatrix(std::cout, C, "C after .invert()", "--");
      return C;
  }
  //-------------------------------------------------------------------------------


  //--- Definition of transversely isotropic elasticity tensor (in voigt notation)
  // - Input: (Youngs modulus, shear modulus, Poisson ratio): {E1,E2,G12,nu12,nu23}
  // - Output: compliance Matrix
  Dune::FieldMatrix<double,6,6> transversely_isotropic(const VectorRT& framevector1,
                                                 const VectorRT& framevector2,
                                                 const VectorRT& framevector3,
                                                 const Dune::FieldVector<double,5>& p //elastic moduli {E1,E2,G12,nu12,nu23}
                                                 ) 
  {
    // (see https://en.wikipedia.org/wiki/Poisson%27s_ratio)
      auto E_1 = p[0];
      auto E_2 = p[1];
      auto E_3 = E_2;
      auto nu_12 = p[3];
      auto nu_13 = nu_12;
      auto nu_21 = (nu_12/E_1)*E_2;
      auto nu_31 = nu_21;
      auto nu_23 = p[4];
      auto nu_32 = nu_23;
      auto G_12 = p[2];
      auto G_23 = E_2/(2.0*(1+nu_23));
      auto G_31 = G_23;
      //other three poisson ratios are not independent 
      
      //---compliance matrix
      Dune::FieldMatrix<double,6,6> C = {{1.0/E_1      , -nu_21/E_2   , -nu_31/E_3   , 0.0     , 0.0     , 0.0   },
                                   {-nu_12/E_1   , 1.0/E_2      , -nu_32/E_3   , 0.0     , 0.0     , 0.0   },
                                   {-nu_13/E_1   , -nu_23/E_2   , 1.0/E_3      , 0.0     , 0.0     , 0.0   },
                                   {  0.0        ,  0.0         , 0.0          , 1.0/G_23, 0.0     , 0.0   },
                                   {  0.0        ,  0.0         , 0.0          , 0.0     , 1.0/G_31, 0.0   },
                                   {  0.0        ,  0.0         , 0.0          , 0.0     , 0.0     , 1.0/G_12}
                                  };

      // printmatrix(std::cout, C, "C", "--");

      //--- return elasticity tensor 
      C.invert();
      // printmatrix(std::cout, C, "C after .invert()", "--");
      return C;
  }
  //-------------------------------------------------------------------------------


  // --- Definition of transversely isotropic elasticity tensor (in voigt notation)
  // - Input: (Youngs modulus, shear modulus, Poisson ratio): {E1,E2,G12,nu12,nu23}
  // - Output: inverse compliance Matrix
  Dune::FieldMatrix<double,6,6> general_anisotropic(const VectorRT& framevector1,
                                              const VectorRT& framevector2,
                                              const VectorRT& framevector3,
                                              const Dune::FieldMatrix<double,6,6>& C //compliance matrix
                                              ) 
  {
      //--- return elasticity tensor (in Voigt notation)
      auto tmp = C;
      tmp.invert();  
      // printmatrix(std::cout, C, "C after .invert()", "--");
      return tmp;
  }
  //-------------------------------------------------------------------------------


  // --- Helpers
  static Dune::FieldVector<double,6> MatrixToVoigt(const MatrixRT& G)
  {
    // return {G[0][0], G[1][1], G[2][2], G[1][2], G[0][2], G[0][1]};
    return {G[0][0], G[1][1], G[2][2], 2.0*G[1][2], 2.0*G[0][2], 2.0*G[0][1]};

  }

  static MatrixRT VoigtToMatrix(const Dune::FieldVector<double,6>& v)
  {
    // return {{v[0], v[5], v[4]}, {v[5], v[1], v[3]}, {v[4], v[3], v[2]}};
    return {{v[0], v[5]/2.0, v[4]/2.0}, {v[5]/2.0, v[1], v[3]/2.0}, {v[4]/2.0, v[3]/2.0, v[2]}};
  }



  MatrixRT applyElasticityTensor(const MatrixRT& G, const int& phase)  const  
  {
    // FieldVector<double,6> G_tmp = {G[0][0], G[1][1], G[2][2], sqrt(2.0)*G[1][2], sqrt(2.0)*G[0][2], sqrt(2.0)*G[0][1]};
    // FieldVector<double,6> G_tmp = {G[0][0], G[1][1], G[2][2], G[1][2], G[0][2], G[0][1]};
    Dune::FieldVector<double,6> G_tmp = MatrixToVoigt(G);
    Dune::FieldVector<double,6> out(0);
    // printvector(std::cout, G_tmp, "G_tmp", "--");

    if (phase == 1)
    {
      // printmatrix(std::cout, L1_, "L1_", "--");
      L1_.mv(G_tmp,out);
    }
    else if (phase == 2)
    {
      // printmatrix(std::cout, L2_, "L2_", "--");
      L2_.mv(G_tmp,out);
    }
    else 
    {
      // printmatrix(std::cout, L3_, "L3_", "--");
      L3_.mv(G_tmp,out);
    }


    // printvector(std::cout, out, "out", "--");
    return VoigtToMatrix(out);
    // return image as Matrix again
    // return {{out[0], (1.0/sqrt(2.0))*out[5], (1.0/sqrt(2.0))*out[4]}, {(1.0/sqrt(2.0))*out[5], out[1], (1.0/sqrt(2.0))*out[3]}, {(1.0/sqrt(2.0))*out[4], (1.0/sqrt(2.0))*out[3], out[2]}};
    // return {{out[0], (1.0/2.0)*out[5], (1.0/2.0)*out[4]}, {(1.0/2.0)*out[5], out[1], (1.0/2.0)*out[3]}, {(1.0/2.0)*out[4], (1.0/2.0)*out[3], out[2]}};
    // return {{out[0], out[5], out[4]}, {out[5], out[1], out[3]}, {out[4], out[3], out[2]}};
  }


  ////////////////////////////////////////////////////////////////////////////////////////
  MatrixRT prestrain(const int& phase,const Domain& x) const  
  {
    if (phase == 1)
      return prestrain1_(x);
    else if (phase == 2)
      return prestrain2_(x);
    else if (phase ==3)
      return prestrain3_(x);
    else 
      DUNE_THROW(Dune::Exception, "Phase number not implemented.");
     
  }





////////////////////////////////////////////////////////


  //--- Apply elasticityTensor_ to input Matrix G at material phase
 // input: Matrix G, material-phase
  MatrixRT ElasticityTensor(const MatrixRT& G, const int& phase) const      //deprecated
  {
    return elasticityTensor_(G,phase);   
  }

  int IndicatorFunction(const Domain& x) const
  {
    return indicatorFunction_(x);
  }


    ///////////////////////////////
    // Getter
    ///////////////////////////////
    Dune::ParameterTree getParameterSet() const {return parameterSet_;}
    // Func2Tensor getElasticityTensor() const {return elasticityTensor_;}
    // Func2TensorParam getElasticityTensor() const {return elasticityTensor_;}
    Func2TensorPhase getElasticityTensor() const {return elasticityTensor_;}
    MatrixPhase getPrestrainFunction() const {return prestrain_;} 

    auto getLocalIndicatorFunction() const {return localIndicatorFunction_;}   //get as localFunction
    auto getIndicatorFunctionGVF() const {return indicatorFunctionGVF_;}   //get as GridViewFunction
    auto getIndicatorFunction() const {return indicatorFunction_;}

    //getLameParameters()? .. Throw Exception if isotropic_ = false! 

  ///////////////////////////////
  // VTK - Writer
  ///////////////////////////////
  // -----------------------------------------------------------------
  // --- write material (grid)functions to VTK
  void writeVTKMaterialFunctions(std::array<int, dim> nElements, const int level)
  {
        std::string outputPath = parameterSet_.get("outputPath", "../../outputs");
        using VTKGridType = Dune::YaspGrid<dim, Dune::EquidistantOffsetCoordinates<double, dim> >;
        // VTKGridType grid_VTK({-1.0/2.0, -1.0/2.0, -1.0/2.0},{1.0/2.0, 1.0/2.0, 1.0/2.0},{80,80,80});
        VTKGridType grid_VTK({-1.0/2.0, -1.0/2.0, -1.0/2.0},{1.0/2.0, 1.0/2.0, 1.0/2.0},nElements);
        
        using GridViewVTK = typename VTKGridType::LeafGridView;
        const GridViewVTK gridView_VTK = grid_VTK.leafGridView();
        
        auto scalarP0FeBasis = makeBasis(gridView_VTK,lagrange<0>());
        auto scalarP1FeBasis = makeBasis(gridView_VTK,lagrange<1>());

        std::vector<double> indicatorFunction_CoeffP0;
        Dune::Functions::interpolate(scalarP0FeBasis, indicatorFunction_CoeffP0, indicatorFunction_);
        auto indicatorFunction_P0 = Dune::Functions::makeDiscreteGlobalBasisFunction<double>(scalarP0FeBasis, indicatorFunction_CoeffP0);
        
        std::vector<double> indicatorFunction_CoeffP1;
        Dune::Functions::interpolate(scalarP1FeBasis, indicatorFunction_CoeffP1, indicatorFunction_);
        auto indicatorFunction_P1 = Dune::Functions::makeDiscreteGlobalBasisFunction<double>(scalarP1FeBasis, indicatorFunction_CoeffP1);
        
        Dune::VTKWriter<GridView> MaterialVtkWriter(gridView_VTK);
        
        MaterialVtkWriter.addCellData(
            indicatorFunction_P0,
            Dune::VTK::FieldInfo("indicatorFunction_P0", Dune::VTK::FieldInfo::Type::scalar, 1));
        MaterialVtkWriter.addVertexData(
            indicatorFunction_P1,
            Dune::VTK::FieldInfo("indicatorFunction_P1", Dune::VTK::FieldInfo::Type::scalar, 1));
        
        MaterialVtkWriter.write(outputPath + "/MaterialFunctions-level"+ std::to_string(level) );
        std::cout << "wrote data to file:" + outputPath +"/MaterialFunctions-level" + std::to_string(level) << std::endl;  
	      return;
  };

  // -----------------------------------------------------------------
  // --- write prestrain (grid)functions to VTK
  // void writeVTKPrestainFunctions(std::array<int, dim> nElements, const int level)
  // {
  //       std::string outputPath = parameterSet_.get("outputPath", "../../outputs");
  //       using VTKGridType = YaspGrid<dim, EquidistantOffsetCoordinates<double, dim> >;
  //       //VTKGridType grid_VTK({-1.0/2.0, -1.0/2.0, -1.0/2.0},{1.0/2.0, 1.0/2.0, 1.0/2.0},{80,80,80});
  //       VTKGridType grid_VTK({-1.0/2.0, -1.0/2.0, -1.0/2.0},{1.0/2.0, 1.0/2.0, 1.0/2.0},nElements);
        
  //       using GridViewVTK = VTKGridType::LeafGridView;
  //       const GridViewVTK gridView_VTK = grid_VTK.leafGridView();
        
  //       auto scalarP0FeBasis = makeBasis(gridView_VTK,lagrange<0>());
  //       auto scalarP1FeBasis = makeBasis(gridView_VTK,lagrange<1>());

  //       std::vector<double> prestrainFunction_CoeffP0;
  //       Functions::interpolate(scalarP0FeBasis, prestrainFunction_CoeffP0, prestrain_);
  //       auto prestrainFunction_P0 = Functions::makeDiscreteGlobalBasisFunction<double>(scalarP0FeBasis, prestrainFunction_CoeffP0);
        
  //       std::vector<double> prestrainFunction_CoeffP1;
  //       Functions::interpolate(scalarP1FeBasis, prestrainFunction_CoeffP1, prestrain_);
  //       auto prestrainFunction_P1 = Functions::makeDiscreteGlobalBasisFunction<double>(scalarP1FeBasis, prestrainFunction_CoeffP1);
        
  //       VTKWriter<GridView> MaterialVtkWriter(gridView_VTK);
        
  //       MaterialVtkWriter.addVertexData(
  //           prestrainFunction_P0,
  //           VTK::FieldInfo("prestrainFunction_P0", VTK::FieldInfo::Type::scalar, 1));    
  //       MaterialVtkWriter.addCellData(
  //           prestrainFunction_P1,
  //           VTK::FieldInfo("prestrainFunction_P1", VTK::FieldInfo::Type::scalar, 1));    

  //       MaterialVtkWriter.write(outputPath + "/PrestrainFunctions-level"+ std::to_string(level) );
  //       std::cout << "wrote data to file:" + outputPath + "/PrestrainFunctions-level" + std::to_string(level) << std::endl;  
	//       return;
  // };

  // void writeVTKPrestainFunctions(std::array<int, dim> nElements, const int level)
  // {
//     using VTKGridType = YaspGrid<dim, EquidistantOffsetCoordinates<double, dim> >;
// //     VTKGridType grid_VTK({-1.0/2.0, -1.0/2.0, -1.0/2.0},{1.0/2.0, 1.0/2.0, 1.0/2.0},{80,80,80});
// //     VTKGridType grid_VTK({-1.0/2.0, -1.0/2.0, -1.0/2.0},{1.0/2.0, 1.0/2.0, 1.0/2.0},{40,40,40});
//     VTKGridType grid_VTK({-1.0/2.0, -1.0/2.0, -1.0/2.0},{1.0/2.0, 1.0/2.0, 1.0/2.0},nElements);
//     using GridViewVTK = VTKGridType::LeafGridView;
//     const GridViewVTK gridView_VTK = grid_VTK.leafGridView();
   
//     FTKfillerContainer<dim> VTKFiller;
//     VTKFiller.vtkPrestrainNorm(gridView_VTK, B_Term, "PrestrainBNorm");
    
//     // WORKS Too 
//     VTKFiller.vtkProblemCell(gridView_VTK, B_Term, muLocal,"VTKProblemCell");;
    
    
//     // TEST 
//     auto scalarP0FeBasis = makeBasis(gridView_VTK,lagrange<0>());
//     auto scalarP1FeBasis = makeBasis(gridView_VTK,lagrange<1>());

//     std::vector<double> B_CoeffP0;
//     Functions::interpolate(scalarP0FeBasis, B_CoeffP0, B_Term);
//     auto B_DGBF_P0 = Functions::makeDiscreteGlobalBasisFunction<double>(scalarP0FeBasis, B_CoeffP0);
        

        
//     VTKWriter<GridView> PrestrainVtkWriter(gridView_VTK);
         
//     PrestrainVtkWriter.addCellData(
//             B_DGBF_P0,
//             VTK::FieldInfo("B_P0", VTK::FieldInfo::Type::scalar, 1));     
        
//     PrestrainVtkWriter.write(outputPath + "/PrestrainFunctions-level"+ std::to_string(level) );
//     std::cout << "wrote data to file:" + outputPath +"/PrestrainFunctions-level" + std::to_string(level) << std::endl; 

//    // };
  
  // MatrixRT ElasticityTensorGlobal(const MatrixRT& G, const Domain& x) const  //global Version (takes global coordinates)
  // {
  //   return MAT(G,x);
  // }

  // //--- apply elasticityTensor_ to input Matrix G at position x
  // MatrixRT applyElasticityTensor(const MatrixRT& G, const Domain& x) const  //global Version (takes global coordinates)
  // {
  //   // Python::Module module = Python::import("material");
  //   // auto indicatorFunctionTest = Python::make_function<double>(module.get("indicatorFunction"));
  //   // return elasticityTensor_(G,indicatorFunctionTest(x));
  //   // auto IFunction = localFunction(indicatorFunction_);
  //   // auto IFunction = this->getIndicatorFunction();
  //   // auto tmp = Dune::Functions::makeGridViewFunction(indicatorFunction_material_1, gridView_);
  //   // auto tmpLocal = LocalF
  //   // return elasticityTensor_(G,IFunction(x));
  //   // return elasticityTensor_(G,localIndicatorFunction_(x));
  //   return elasticityTensor_(G,indicatorFunction_(x));
  //   // return elasticityTensor_(G,indicatorFunctionGVF_(x));
  //   // int phase = indicatorFunction_(x);
  //   // auto tmp = elasticityTensor_(G,phase);
  //   // auto tmp = material_1(G,indicatorFunction_(x));
  //   // printmatrix(std::cout, material_1(G,indicatorFunction_(x)), "material_1(G,indicatorFunction_(x))", "--");
  //   // printmatrix(std::cout, elasticityTensor_(G,phase), "elasticityTensor_(G,phase)", "--");
  // }


//  template<class Domain>
// static MatrixRT MAT(const MatrixRT& G, const Domain& x)      //unfortunately not possible as a GridViewFunction? 
//   {
//       const FieldVector<double,3> mu = {80.0, 80.0, 60.0};
//       const FieldVector<double,3> lambda = {80.0, 80.0, 25.0};
//       double theta=0.25;
//       if (x[0] <(-0.5+theta) and x[2]<(-0.5+theta))
//          return 2.0 * mu[0] * sym(G) + lambda[0] * trace(sym(G)) * Id();
//       else if (x[1]<(-0.5+theta) and x[2]>(0.5-theta))
//          return 2.0 * mu[1] * sym(G) + lambda[1] * trace(sym(G)) * Id();
//       else 
//          return 2.0 * mu[2] * sym(G) + lambda[2] * trace(sym(G)) * Id();
//   }



}; // end class


#endif