#ifndef HEAT_MODELS_HH
#define HEAT_MODELS_HH

#include <dune/fem/version.hh>
#include <dune/fem/misc/fmatrixconverter.hh>
#include <dune/fem/io/parameter.hh>


#include <dune/fem-dg/operator/limiter/limitpass.hh>

// local includes
#include <dune/fem-dg/models/defaultmodel.hh>


/**********************************************
 * Analytical model                           *
 *********************************************/

/**
 * @brief Traits class for HeatEqnModel
 */
template <class GridPart, int dimR > 
class HeatEqnModelTraits
{
public:
  typedef GridPart                                                   GridPartType;
  typedef typename GridPartType :: GridType                          GridType;
  static const int dimDomain = GridType::dimensionworld;
  static const int dimRange = dimR;
  static const int dimGradRange = dimRange * dimDomain ;
  // Definition of domain and range types
  typedef Dune::FieldVector< double, dimDomain >                     DomainType;
  typedef Dune::FieldVector< double, dimDomain-1 >                   FaceDomainType;
  typedef Dune::FieldVector< double, dimRange >                      RangeType;
  typedef Dune::FieldVector< double, dimGradRange >                  GradientType;
  // ATTENTION: These are matrices (c.f. HeatEqnModel)
  typedef Dune::FieldMatrix< double, dimRange, dimDomain >           FluxRangeType;
  typedef Dune::FieldMatrix< double, dimRange, dimDomain >           JacobianRangeType;
  typedef Dune::FieldMatrix< double, dimGradRange, dimDomain >       DiffusionRangeType;
  typedef Dune::FieldMatrix< double, dimDomain, dimDomain >          DiffusionMatrixType;
  typedef typename GridType :: template Codim< 0 > :: Entity         EntityType;
  typedef typename GridPartType :: IntersectionIteratorType          IntersectionIterator;
  typedef typename IntersectionIterator :: Intersection              IntersectionType;

  //typedef Dune::Fem::MinModLimiter< FieldType > LimiterFunctionType ;
  //typedef SuperBeeLimiter< FieldType > LimiterFunctionType ;
  //typedef VanLeerLimiter< FieldType > LimiterFunctionType ;
};

/**
 * @brief describes the analytical model
 *
 * This is an description class for the problem
 * \f{eqnarray*}{ V + \nabla a(U)      & = & 0 \\
 * \partial_t U + \nabla (F(U)+A(U,V)) & = & 0 \\
 *                          U          & = & g_D \\
 *                   \nabla U \cdot n  & = & g_N \f}
 *
 * where each class methods describes an analytical function.
 * <ul>
 * <li> \f$F\f$:   advection() </li>
 * <li> \f$a\f$:   diffusion1() </li>
 * <li> \f$A\f$:   diffusion2() </li>
 * <li> \f$g_D\f$  boundaryValue() </li>
 * <li> \f$g_N\f$  boundaryFlux1(), boundaryFlux2() </li>
 * </ul>
 *
 * \attention \f$F(U)\f$ and \f$A(U,V)\f$ are matrix valued, and therefore the
 * divergence is defined as
 *
 * \f[ \Delta M = \nabla \cdot (\nabla \cdot (M_{i\cdot})^t)_{i\in 1\dots n} \f]
 *
 * for a matrix \f$M\in \mathbf{M}^{n\times m}\f$.
 *
 * @param GridPart GridPart for extraction of dimension
 * @param ProblemType Class describing the initial(t=0) and exact solution
 */


////////////////////////////////////////////////////////
//
//  Analytical model for the Heat Equation
//      dt(u) + div(uV) - epsilon*lap(u)) = 0
//  where V is constant vector
//
////////////////////////////////////////////////////////
template <class GridPartType,class ProblemImp>
class HeatEqnModel : 
  public DefaultModel < HeatEqnModelTraits< GridPartType,ProblemImp::dimRange > >
{
public:
  // for heat equations advection is disabled 
  static const bool hasAdvection = true ;
  static const bool hasDiffusion = true ;

  typedef ProblemImp ProblemType ;

  static const int ConstantVelocity = ProblemType :: ConstantVelocity;
  typedef typename GridPartType :: GridType                        GridType;
  typedef HeatEqnModelTraits< GridPartType,ProblemImp::dimRange >  Traits;
  static const int dimDomain = Traits :: dimDomain ; 
  static const int dimRange  = Traits :: dimRange ;
  typedef typename Traits :: DomainType                          DomainType;
  typedef typename Traits :: RangeType                           RangeType;
  typedef typename Traits :: GradientType                        GradientType;
  typedef typename Traits :: FluxRangeType                       FluxRangeType;
  typedef typename Traits :: DiffusionRangeType                  DiffusionRangeType;
  typedef typename Traits :: DiffusionMatrixType                 DiffusionMatrixType;
  typedef typename Traits :: FaceDomainType                      FaceDomainType;
  typedef typename Traits :: JacobianRangeType                   JacobianRangeType;

  typedef typename Traits :: EntityType                          EntityType;
  typedef typename Traits :: IntersectionType                    IntersectionType;

  HeatEqnModel(const HeatEqnModel& otehr);
  const HeatEqnModel &operator=(const HeatEqnModel &other);
public:
  /**
   * @brief Constructor
   *
   * initializes model parameter
   *
   * @param problem Class describing the initial(t=0) and exact solution
   */
  HeatEqnModel(const ProblemType& problem) :
    problem_(problem),
    epsilon_(problem.epsilon()),
    tstepEps_( getTStepEps() ),
    velocity_( getVelocity() )
  {}

  inline bool hasFlux() const { return true ; }
  inline const ProblemType& problem() const { return problem_; }
  inline bool hasStiffSource() const { return problem_.hasStiffSource(); }
  inline bool hasNonStiffSource() const { return problem_.hasNonStiffSource(); }

  inline double nonStiffSource( const EntityType& en,
                        const double time,
                        const DomainType& x,
                        const RangeType& u,
                        const GradientType& du,
                        RangeType & s) const
  {
    //FieldMatrixConverter< GradientType, JacobianRangeType> jac( du );
    return nonStiffSource( en, time, x, u, s );
  }

  inline double nonStiffSource( const EntityType& en,
                        const double time,
                        const DomainType& x,
                        const RangeType& u,
                        const JacobianRangeType& jac,
                        RangeType & s) const
  {
    return nonStiffSource( en, time, x, u, s );
  }

  inline double nonStiffSource( const EntityType& en,
                        const double time,
                        const DomainType& x,
                        const RangeType& u,
                        RangeType & s) const
  {
    DomainType xgl = en.geometry().global( x );
    return problem_.nonStiffSource( xgl, time, u, s );
  }

  inline double stiffSource( const EntityType& en,
                        const double time,
                        const DomainType& x,
                        const RangeType& u,
                        const GradientType& du,
                        RangeType & s) const
  {
    //FieldMatrixConverter< GradientType, JacobianRangeType> jac( du );
    return stiffSource( en, time, x, u, s );
  }


  inline double stiffSource( const EntityType& en,
                        const double time,
                        const DomainType& x,
                        const RangeType& u,
                        const JacobianRangeType& jac,
                        RangeType & s) const
  {
    return stiffSource( en, time, x, u, s );
  }

  inline double stiffSource( const EntityType& en,
                        const double time,
                        const DomainType& x,
                        const RangeType& u,
                        RangeType & s) const
  {
    DomainType xgl = en.geometry().global( x );
    return problem_.stiffSource( xgl, time, u, s );
  }

  /**
   * @brief advection term \f$F\f$
   *
   * @param en entity on which to evaluate the advection term
   * @param time current time of TimeProvider
   * @param x coordinate local to entity
   * @param u \f$U\f$
   * @param f \f$f(U)\f$
   */
  inline  void advection(const EntityType& en,
                         const double time,
                         const DomainType& x,
                         const RangeType& u,
                         FluxRangeType & f) const
  {
    // evaluate velocity V
    DomainType v;
    velocity( en, time, x, u, v );

    // f = uV;
    for( int r=0; r<dimRange; ++r )
      for( int d=0; d<dimDomain; ++d )
        f[r][d] = v[ d ] * u[ r ];
  }

  /**
   * @brief velocity calculation, is called by advection()
   */
  inline  void velocity(const EntityType& en,
                        const double time,
                        const DomainType& x,
                        const RangeType& u, 
                        DomainType& v) const
  {
    velocity( en.geometry().global(x), time, v );
  }

  /*
   * @brief velocity calculation, is called by advection()
   */
  inline  void velocity(const DomainType& xGlobal,
                        const double time,
                        DomainType& v) const
  {
    problem_.velocity(xGlobal, time, v);
  }

  /**
   * @brief diffusion term \f$a\f$
   */
  inline void jacobian(const EntityType& en,
                        const double time,
                        const DomainType& x,
                        const RangeType& u,
                        DiffusionRangeType& a) const
  {
    a = 0;

    assert( a.rows == dimRange * dimDomain );
    assert( a.cols == dimDomain );

    for (int r=0;r<dimRange;r++)
      for (int d=0;d<dimDomain;d++)
        a[dimDomain*r+d][d] = u[r];
  }

  inline void eigenValues(const EntityType& en,
                          const double time,
                          const DomainType& x,
                          const RangeType& u,
                          RangeType& maxValue) const
  {
    FluxRangeType A(0);
    maxValue = RangeType( problem_.diffusion( u,A ) );
    return;
    abort();
    /*
    DiffusionMatrixType K(0) ;
    DomainType values ;
    for( int r = 0; r<dimRange; ++r )
    {
      // setup matrix 
      for(int i=0; i<dimDomain; ++i) 
        K[i][i] = problem_.diffusion( u,A );

      // calculate eigenvalues 
      Dune::FMatrixHelp :: eigenValues( K, values );
      // take max eigenvalue 
      maxValue[ r ] = values.infinity_norm();
    }
    */
  }

  /**
   * @brief diffusion term \f$A\f$
   */
  template <class JacobianType>
  inline void diffusion(const EntityType& en,
                        const double time,
                        const DomainType& x,
                        const RangeType& u,
                        const JacobianType& jac,
                        FluxRangeType& A) const
  {
    // copy v to A 
    A = jac;

    // apply diffusion coefficient 
    //double d =  0.; //(1.-en.geometry().global(x)[0])*en.geometry().global(x)[0]+
    //                //(1.-en.geometry().global(x)[1])*en.geometry().global(x)[1];
    A *= problem_.diffusion( u, A );//*(1.+d);
  }

  inline void diffusion(const EntityType& en,
                        const double time,
                        const DomainType& x,
                        const RangeType& u,
                        const GradientType& vecJac,
                        FluxRangeType& A) const
  {
    Dune::Fem::FieldMatrixConverter< GradientType, FluxRangeType> jac( vecJac );
    diffusion( en, time, x, u, jac, A );
  }

  inline double diffusionTimeStep( const IntersectionType &it,
                                   const double enVolume,
                                   const double circumEstimate,
                                   const double time,
                                   const FaceDomainType& x,
                                   const RangeType& u ) const
  {
    return tstepEps_;
  }

  /** \brief convert conservative to primitive variables
   *  \note This method is used only for additional output to hdd
   */
  inline void conservativeToPrimitive( const double time,
                                       const DomainType& xgl,
                                       const RangeType& cons, 
                                       RangeType& prim,
                                       const bool ) const
  {
    prim = cons;
  }

public:                            
  /**
   * @brief checks for existence of dirichlet boundary values
   */
  inline bool hasBoundaryValue(const IntersectionType& it,
                               const double time,
                               const FaceDomainType& x) const
  {
    return true;
  }

  /**
   * @brief neuman boundary values \f$g_N\f$ for pass2
   */
  inline double boundaryFlux(const IntersectionType& it,
                             const double time,
                             const FaceDomainType& x,
                             const RangeType& uLeft,
                             const GradientType& vLeft,
                             RangeType& gLeft) const
  {
    gLeft = 0.;
    return 0.;
  }

  /**
   * @brief neuman boundary values \f$g_N\f$ for pass1
   */
  inline double boundaryFlux(const IntersectionType& it,
                             const double time,
                             const FaceDomainType& x,
                             const RangeType& uLeft,
                             RangeType& gLeft) const
  {
    gLeft = 0.;
    return 0.;
  }

  inline double diffusionBoundaryFlux( const IntersectionType& it,
                                       const double time,
                                       const FaceDomainType& x,
                                       const RangeType& uLeft,
                                       const GradientType& gradLeft,
                                       RangeType& gLeft ) const  
  {
    Dune::Fem::FieldMatrixConverter< GradientType, JacobianRangeType> jacLeft( gradLeft );
    return diffusionBoundaryFlux( it, time, x, uLeft, jacLeft, gLeft );
  }

  /** \brief boundary flux for the diffusion part
   */
  template <class JacobianRangeImp>
  inline double diffusionBoundaryFlux( const IntersectionType& it,
                                       const double time,
                                       const FaceDomainType& x,
                                       const RangeType& uLeft,
                                       const JacobianRangeImp& jacLeft,
                                       RangeType& gLeft ) const  
  {
    std::cerr <<"diffusionBoundaryFlux shouldn't be used for this testcase" <<std::endl;
    abort();
  }

  /**
   * @brief dirichlet boundary values
   */
  inline  void boundaryValue(const IntersectionType& it,
                             const double time,
                             const FaceDomainType& x,
                             const RangeType& uLeft,
                             RangeType& uRight) const
  {
#ifdef TESTOPERATOR
  uRight = 0;
  return;
#endif
    DomainType xgl = it.geometry().global( x );
    problem_.evaluate(xgl, time, uRight);
  }


  // here x is in global coordinates
  inline void maxSpeed( const EntityType& entity,
                        const double time,
                        const DomainType& xGlobal,
                        const DomainType& normal,
                        const RangeType& u,
                        double& advspeed,
                        double& totalspeed ) const
  {
    DomainType v;
    problem_.velocity( xGlobal, time, v );
    advspeed   = v * normal ;
    totalspeed = advspeed;
  }

 protected:
  DomainType getVelocity() const 
  {
    DomainType velocity(0);
    if(ConstantVelocity) {
      problem_.velocity(velocity,velocity);
    }
    return velocity;  
  }
  double getTStepEps() const 
  {
    // if diffusionTimeStep is set to non-zero in the parameterfile, the
    // deltaT in the timeprovider is updated according to the diffusion
    // parameter epsilon.
    bool diff_tstep;
    Dune::Fem::Parameter::get("femhowto.diffusionTimeStep", diff_tstep);
    return diff_tstep ? epsilon_ : 0;
  }

 protected:
  const ProblemType& problem_;
  const double epsilon_;
  const double tstepEps_;
 public:
  const DomainType velocity_;

};


/**
 * @brief defines the advective flux
 */
template <class ModelType>
class UpwindFlux {
public:
  typedef ModelType Model;
  typedef typename Model::Traits Traits;
  enum { dimRange = Model::dimRange };
  typedef typename Model :: DomainType DomainType;
  typedef typename Model :: RangeType RangeType;
  typedef typename Model :: FluxRangeType FluxRangeType;
  typedef typename Model :: DiffusionRangeType DiffusionRangeType;
  typedef typename Model :: FaceDomainType  FaceDomainType;
  typedef typename Model :: EntityType  EntityType;
  typedef typename Model :: IntersectionType  IntersectionType;
protected:
  template <class Model, bool constVelo>
    struct Velocity
    {
      /**
       * @brief computes and returns the wind direction
       */
      static inline double upwind(const Model& model,
                                  const IntersectionType& it,
                                  const double time,
                                  const FaceDomainType& x,
                                  const RangeType& uLeft)
      {
        const DomainType normal = it.integrationOuterNormal(x);
        DomainType velocity;
        model.velocity(*it.inside(),time,
                       it.geometryInInside().global(x),
                       uLeft,velocity);
        return normal*velocity;
      }
    };

  template <class Model>
    struct Velocity<Model,true>
    {
      /**
       * @brief computes and returns the wind direction for models with
       * constant velocity
       */
      static inline double upwind( const Model& model,
                                   const IntersectionType& it,
                                   const double time,
                                   const FaceDomainType& x,
                                   const RangeType& uLeft )
      {
        const DomainType normal = it.integrationOuterNormal(x);
        return normal * model.velocity_;
      }
    };

public:
  /**
   * @brief Constructor
   */
  UpwindFlux(const Model& mod) : model_(mod) {}

  static std::string name () { return "UpwindFlux"; }

  const Model& model() const {return model_;}

  /**
   * @brief evaluates the flux \f$g(u,v)\f$
   *
   * @return maximum wavespeed * normal
   */
  template <class QuadratureImp>
  inline double numericalFlux( const IntersectionType& it,
                               const EntityType& inside,
                               const EntityType& outside,
                               const double time,
                               const QuadratureImp& faceQuadInner,
                               const QuadratureImp& faceQuadOuter,
                               const int quadPoint,
                               const RangeType& uLeft,
                               const RangeType& uRight,
                               RangeType& gLeft,
                               RangeType& gRight ) const
  {
    const FaceDomainType& x = faceQuadInner.localPoint( quadPoint );
    const double upwind = Velocity<Model,Model::ConstantVelocity>::
      upwind(model_,it,time,x,uLeft);

    if (upwind>0)
      gLeft = uLeft;
    else
      gLeft = uRight;
    gLeft *= upwind;
    gRight = gLeft;
    return std::abs(upwind);
  }
protected:
  const Model& model_;
};

#endif