// SpaFEDTe, a Template based C++ library for creating 
// Discontinuous Finite Element Spaces,
// Copyright (C) 2012 Lorenzo Alessio Botti

/* This library is free software; you can redistribute it and/or */
/* modify it under the terms of the GNU Lesser General Public */ 
/* License as published by the Free Software Foundation either */ 
/* version 3.0 of the License, or (at your option) any later version. */

/* This software 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 */
/* Lesser General Public License for more details. */

/* You should have received a copy of the GNU Lesser General Public */
/* License along with this software; if not, a copy of the full */
/* GNU Lesser General Public License can be found at */
/* http://www.gnu.org/licenses/. */

#ifndef __finite_element_basis_h__
#define __finite_element_basis_h__

#include "BasisFunction.hpp"
#include "Quadrature.hpp"
#include "EnumElemType.hpp"
#include "Tuple.hpp"

template<class FiniteElementFunctionT>
class FiniteElementBasis
{
public:
  typedef FiniteElementFunctionT FiniteElementFunctionType;
  typedef Quadrature::PointVector PointVector;
  
  FiniteElementBasis() 
    {
      ;
    }
  
  ~FiniteElementBasis() { }
  
public:
  template<unsigned int Dim> static void init(BasisFunction<Dim>& basisFunction, const Quadrature& quadrature, const ElemType itemType, const unsigned int order)
  {
    assert(itemType == quadrature.getItemType());
    ElemType& b_itemType = basisFunction.getItemType();
    unsigned int& b_Order = basisFunction.getOrder();
    unsigned int& q_Order = basisFunction.getQorder();
    if (itemType == b_itemType && order == b_Order && quadrature.getOrder() == q_Order)
    {
      return;
    }
    else 
    {
      b_itemType = itemType;
      b_Order = order;
      q_Order = quadrature.getOrder();
    }
    FiniteElementFunctionType::initBasisFunction(basisFunction, quadrature.getQpoint());
  }
  
  template<unsigned int Dim> static void init(BasisFunction<Dim>& basisFunction, const Quadrature& quadrature, const ElemType itemType, const unsigned int order, 
                                              typename BasisFunction<Dim>::ShapeDerivMatrix& _shapeDerivMatrixCopy)
  {
    assert(itemType == quadrature.getItemType());
    ElemType& b_itemType = basisFunction.getItemType();
    unsigned int& b_Order = basisFunction.getOrder();
    unsigned int& q_Order = basisFunction.getQorder();
    if (itemType == b_itemType && order == b_Order && quadrature.getOrder() == q_Order)
    {
      return;
    }
    else 
    {
      b_itemType = itemType;
      b_Order = order;
      q_Order = quadrature.getOrder();
    }
    FiniteElementFunctionType::initBasisFunction(basisFunction, quadrature.getQpoint());
    for (unsigned int d = 0; d < Dim; d++)
    {
      _shapeDerivMatrixCopy(d) = basisFunction.getShapeFunctionDeriv()(d);
    }
  }
  
  template<unsigned int Dim> static void init(BasisFunction<Dim>& basisFunction, const Quadrature& quadrature, const ElemType itemType, const unsigned int order, 
                                              typename BasisFunction<Dim>::ShapeMatrix& _shapeMatrixCopy)
  {
    assert(itemType == quadrature.getItemType());
    ElemType& b_itemType = basisFunction.getItemType();
    unsigned int& b_Order = basisFunction.getOrder();
    unsigned int& q_Order = basisFunction.getQorder();
    if (itemType == b_itemType && order == b_Order && quadrature.getOrder() == q_Order)
    {
      return;
    }
    else 
    {
      b_itemType = itemType;
      b_Order = order;
      q_Order = quadrature.getOrder();
    }
    FiniteElementFunctionType::initBasisFunction(basisFunction, quadrature.getQpoint());
    _shapeMatrixCopy = basisFunction.getShapeFunction();
  }
  
  template<unsigned int Dim> static void reInit(BasisFunction<Dim>& basisFunction, const Quadrature& quadrature, const ElemType itemType, const unsigned int order)
  {
    assert(itemType == quadrature.getItemType());
    basisFunction.getItemType() = itemType;
    basisFunction.getOrder() = order;
    basisFunction.getQorder() = 0;
    FiniteElementFunctionType::initBasisFunction(basisFunction, quadrature.getQpoint());
  }

  template<unsigned int Dim> static void reInit(BasisFunction<Dim>& basisFunction, const typename Quadrature::PointVector& quadratureVec, const ElemType itemType, const unsigned int order)
  {
    basisFunction.getItemType() = itemType;
    basisFunction.getOrder() = order;
    basisFunction.getQorder() = 0;
    FiniteElementFunctionType::initBasisFunction(basisFunction, quadratureVec);
  }
    
  template<unsigned int Dim> static void init(BasisFunction<Dim>& basisFunction, const typename Quadrature::PointVector& quadratureVec, const ElemType itemType, const unsigned int order)
  {
    ElemType& b_itemType = basisFunction.getItemType();
    unsigned int& b_Order = basisFunction.getOrder();
    unsigned int& q_Order = basisFunction.getQorder();
    q_Order = 0;
    if (itemType == b_itemType && order == b_Order)
    {
      return;
    }
    else 
    {
      b_itemType = itemType;
      b_Order = order;
    }
    FiniteElementFunctionType::initBasisFunction(basisFunction, quadratureVec);
  }
 
  template<unsigned int Dim> static void reInit(BasisFunction<Dim>& basisFunction, const Point3D& point, const ElemType itemType, const unsigned int order)
  {
    basisFunction.getItemType() = itemType;
    basisFunction.getOrder() = order;
    basisFunction.getQorder() = 0;
//    PointVector singlePoint = point;
    FiniteElementFunctionType::initBasisFunction(basisFunction, point);  
  }

  static unsigned int dofsNumber (const unsigned int Dim, const unsigned int order, const ElemType itemType = INVALID_ELEM)
  {
    return FiniteElementFunctionType::dofsNumber(Dim, order, itemType);
  }

  static bool is_hierarchical() 
  {
    return FiniteElementFunctionType::is_hierarchical();
  }
};

#endif
