// -*- tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=8 sw=2 sts=2:
#ifndef DUNE_GRID_COMMON_REFINEMENT_PRISMTRIANGULATION_CC
#define DUNE_GRID_COMMON_REFINEMENT_PRISMTRIANGULATION_CC

#include <dune/common/array.hh>
#include <dune/common/fvector.hh>
#include <dune/common/misc.hh>
#include <dune/common/typetraits.hh>

#include <dune/geometry/genericgeometry/geometry.hh>
#include <dune/geometry/genericgeometry/geometrytraits.hh>
#include <dune/geometry/genericgeometry/topologytypes.hh>
#include <dune/geometry/referenceelements.hh>

#include <dune/grid/common/geometry.hh>

#include "base.cc"
#include "simplex.cc"

namespace Dune {

  namespace RefinementImp {

    /*! @brief This namespace contains the @ref Refinement
               implementation for triangulating prisms
               (GeometryType::prism -> GeometryType::simplex)

      
    */
    namespace PrismTriangulation {

      // ////////////
      //
      //  Utilities
      //

      using Simplex::getPermutation;
      using Simplex::referenceToKuhn;

      // ////////////////////////////////////
      //
      //  Refine a prism with simplices
      //

      // forward declaration of the iterator base
      template<int dimension, class CoordType, int codimension>
      class RefinementIteratorSpecial;

      /* 
       * The permutations 0,2 and 3 of the Kuhn-decomposition of a cube into simplices form a prism. 
       * The resulting prism is not oriented the same as the reference prism and so the Kuhn-coordinates 
       * have to be transformed using the method below.
       */
       template<int dimension, class CoordType> FieldVector<CoordType, dimension> transformCoordinate(FieldVector<CoordType, dimension> point) 
       {
           FieldVector<CoordType, dimension> transform;
           transform[0]=point[1];
           transform[1]=1-point[0];
           transform[2]=point[2];
           return transform;
       }




       /** \brief Implementation of the refinement of a prism into simplices.
        *
        * Note that the virtual vertices of two intersecting simplices might have copies, i.e.
        * by running over all vertices using the VertexIterator you might run over some twice. 
        *
       */
      template<int dimension_, class CoordType> 
      class RefinementImp {
                public:
                        enum {dimension = dimension_};

                        struct GridFamily;
                        typedef CoordType ctype;
                        enum {dimensionworld = dimension};

                        template<int codimension> 
                        struct Codim;
                        typedef typename Codim<dimension>::SubEntityIterator VertexIterator;
                        typedef FieldVector<CoordType, dimension> CoordVector;
                        typedef typename Codim<0>::SubEntityIterator ElementIterator;
                        typedef FieldVector<int, dimension+1> IndexVector;

                        static int nVertices(int level);
                        static VertexIterator vBegin(int level);
                        static VertexIterator vEnd(int level);

                        static int nElements(int level);
                        static ElementIterator eBegin(int level);
                        static ElementIterator eEnd(int level);

                private:
                        friend class RefinementIteratorSpecial<dimension, CoordType, 0>;
                        friend class RefinementIteratorSpecial<dimension, CoordType, dimension>;

                        typedef Simplex::RefinementImp<dimension, CoordType> BackendRefinement;
                };
      
      
      template<int dimension, class CoordType>
      template<int codimension>
      struct RefinementImp<dimension, CoordType>::Codim
      {
        class SubEntityIterator;
        typedef Dune::Geometry<dimension-codimension, dimension,
                               RefinementImp<dimension, CoordType>,
                               GenericGeometry::Geometry> Geometry;
      };

      template<int dimension, class CoordType>
      int
      RefinementImp<dimension, CoordType>::
      nVertices(int level)
      {
          return BackendRefinement::nVertices(level) * 3;
      }

      template<int dimension, class CoordType>
      typename RefinementImp<dimension, CoordType>::VertexIterator
      RefinementImp<dimension, CoordType>::
      vBegin(int level)
      {
        return VertexIterator(level);
      }

      template<int dimension, class CoordType>
      typename RefinementImp<dimension, CoordType>::VertexIterator
      RefinementImp<dimension, CoordType>::
      vEnd(int level)
      {
        return VertexIterator(level, true);
      }

      template<int dimension, class CoordType>
      int
      RefinementImp<dimension, CoordType>::
      nElements(int level)
      {
        return BackendRefinement::nElements(level) * 3;
      }

      template<int dimension, class CoordType>
      typename RefinementImp<dimension, CoordType>::ElementIterator
      RefinementImp<dimension, CoordType>::
      eBegin(int level)
      {
        return ElementIterator(level);
      }

      template<int dimension, class CoordType>
      typename RefinementImp<dimension, CoordType>::ElementIterator
      RefinementImp<dimension, CoordType>::
      eEnd(int level)
      {
        return ElementIterator(level, true);
      }

      // //////////////
      //
      // The iterator
      //

      // vertices
      
      template<int dimension, class CoordType>
      class RefinementIteratorSpecial<dimension, CoordType, dimension>
      {
      public:
        typedef RefinementImp<dimension, CoordType> Refinement;
        typedef typename Refinement::CoordVector CoordVector;
        
        RefinementIteratorSpecial(int level, bool end = false);

        void increment();
        
        CoordVector coords() const;
        int index() const;
      protected:
        typedef typename Refinement::BackendRefinement BackendRefinement;
        typedef typename BackendRefinement::template Codim<dimension>::SubEntityIterator BackendIterator;
        enum { nKuhnSimplices = 3 };
        
        int level;

        int kuhnIndex;
        BackendIterator backend;
        const BackendIterator backendEnd;
      };
    
      template<int dimension, class CoordType>
      RefinementIteratorSpecial<dimension, CoordType, dimension>::
      RefinementIteratorSpecial(int level_, bool end)
        : level(level_), kuhnIndex(0),
          backend(BackendRefinement::vBegin(level)),
          backendEnd(BackendRefinement::vEnd(level))
      {
        if(end)
          kuhnIndex = nKuhnSimplices;
      }
    
      template<int dimension, class CoordType>
      void
      RefinementIteratorSpecial<dimension, CoordType, dimension>::
      increment()
      {
        ++backend;
        if(backend == backendEnd) {
          backend = BackendRefinement::vBegin(level);
          ++kuhnIndex;
        }
      }

      template<int dimension, class CoordType>
      typename RefinementIteratorSpecial<dimension, CoordType, dimension>::CoordVector
      RefinementIteratorSpecial<dimension, CoordType, dimension>::
      coords() const
      {
        // while the kuhnIndex runs from 0,1,2 the actual permutations we need are 0,2,3
        return transformCoordinate(referenceToKuhn(backend.coords(),
                                                                getPermutation<dimension>((kuhnIndex+2)%4)));
        
      }

      template<int dimension, class CoordType>
      int
      RefinementIteratorSpecial<dimension, CoordType, dimension>::
      index() const
      { return kuhnIndex*BackendRefinement::nVertices(level) + backend.index(); }

      // elements

      template<int dimension, class CoordType>
      class RefinementIteratorSpecial<dimension, CoordType, 0>
      {
      public:
        typedef RefinementImp<dimension, CoordType> Refinement;
        typedef typename Refinement::IndexVector IndexVector;
        typedef typename Refinement::CoordVector CoordVector;
        typedef typename Refinement::template Codim<0>::Geometry Geometry;
        
        RefinementIteratorSpecial(int level, bool end = false);
      
        void increment();
      
        IndexVector vertexIndices() const;
        int index() const;
        CoordVector coords() const;
        Geometry geometry () const;
      protected:
        typedef typename Refinement::BackendRefinement BackendRefinement;
        typedef typename BackendRefinement::template Codim<0>::SubEntityIterator BackendIterator;
        enum { nKuhnSimplices = 3};

        int level;
        
        int kuhnIndex;
        BackendIterator backend;
        const BackendIterator backendEnd;
      };
    
      template<int dimension, class CoordType>
      RefinementIteratorSpecial<dimension, CoordType, 0>::
      RefinementIteratorSpecial(int level_, bool end)
        : level(level_), kuhnIndex(0),
          backend(BackendRefinement::eBegin(level)),
          backendEnd(BackendRefinement::eEnd(level))
      {
        if(end)
          kuhnIndex = nKuhnSimplices;
      }
    
      template<int dimension, class CoordType>
      void
      RefinementIteratorSpecial<dimension, CoordType, 0>::
      increment()
      {
        ++backend;
        if(backend == backendEnd) {
          backend = BackendRefinement::eBegin(level);
          ++kuhnIndex;
        }
      }

      template<int dimension, class CoordType>
      typename RefinementIteratorSpecial<dimension, CoordType, 0>::IndexVector
      RefinementIteratorSpecial<dimension, CoordType, 0>::
      vertexIndices() const
      {
        IndexVector indices = backend.vertexIndices();

        int base = kuhnIndex * BackendRefinement::nVertices(level);
        indices += base;

        return indices;
      }

      template<int dimension, class CoordType>
      int
      RefinementIteratorSpecial<dimension, CoordType, 0>::
      index() const
      { return kuhnIndex*BackendRefinement::nElements(level) + backend.index(); }

      template<int dimension, class CoordType>
      typename RefinementIteratorSpecial<dimension, CoordType, 0>::CoordVector
      RefinementIteratorSpecial<dimension, CoordType, 0>::
      coords() const
      {
        return geometry()
          .global(GenericReferenceElements<CoordType, dimension>
                  ::simplex().position(0,0));
      }

      template<int dimension, class CoordType>
      typename RefinementIteratorSpecial<dimension, CoordType, 0>::Geometry
      RefinementIteratorSpecial<dimension, CoordType, 0>::geometry () const
      {
        const typename BackendIterator::Geometry &bgeo = backend.geometry();
        Dune::array<CoordVector, dimension+1> corners;
        for(int i = 0; i <= dimension; ++i)
          corners[i] = transformCoordinate(referenceToKuhn(bgeo.corner(i),
                                       getPermutation<dimension>((kuhnIndex+2)%4)));

        return Geometry(GenericGeometry::Geometry
                        <dimension, dimension, Refinement>
                        (bgeo.type(), corners));
      }

      // common

      template<int dimension, class CoordType>
      template<int codimension>
      class RefinementImp<dimension, CoordType>::Codim<codimension>::SubEntityIterator
        : public ForwardIteratorFacade<typename RefinementImp<dimension, CoordType>::template Codim<codimension>::SubEntityIterator, int>,
          public RefinementIteratorSpecial<dimension, CoordType, codimension>
      {
      public:
        typedef RefinementImp<dimension, CoordType> Refinement;
        typedef SubEntityIterator This;

        SubEntityIterator(int level, bool end = false);

        bool equals(const This &other) const;
      protected:
        using RefinementIteratorSpecial<dimension, CoordType, codimension>::kuhnIndex;
        using RefinementIteratorSpecial<dimension, CoordType, codimension>::backend;
      };

#ifndef DOXYGEN
    
      template<int dimension, class CoordType>
      template<int codimension>
      RefinementImp<dimension, CoordType>::Codim<codimension>::SubEntityIterator::
      SubEntityIterator(int level, bool end)
        : RefinementIteratorSpecial<dimension, CoordType, codimension>(level, end)
      {}
    
      template<int dimension, class CoordType>
      template<int codimension>
      bool
      RefinementImp<dimension, CoordType>::Codim<codimension>::SubEntityIterator::
      equals(const This &other) const
      { return kuhnIndex == other.kuhnIndex && backend == other.backend; }

#endif

    } // namespace PrismTriangulation

  } // namespace RefinementImp

  namespace GenericGeometry {

    template< int dimension, class CoordType >
    struct GlobalGeometryTraits
    < RefinementImp::PrismTriangulation::RefinementImp<dimension,
                                                       CoordType> > :
      public DefaultGeometryTraits<CoordType, dimension, dimension>
    {
      //   hybrid   [ true if Codim 0 is hybrid ]
      static const bool hybrid = false;
      //   topologyId [ for Codim 0, needed for (hybrid=false) ]
      static const unsigned topologyId =
        SimplexTopology< dimension >::type::id;
    };

  } // namespace GenericGeometry

  namespace FacadeOptions {

    template<int dimension, class CoordType>
    struct StoreGeometryReference
    < dimension, dimension,
      RefinementImp::PrismTriangulation::RefinementImp<dimension, CoordType>,
      GenericGeometry::Geometry>
    {
      //! Whether to store by reference or by reference.
      static const bool v = false;
    };

  } // namespace FacadeOptions

  namespace RefinementImp {

    // ///////////////////////
    //
    // The refinement traits
    //

#ifndef DOXYGEN
    template<unsigned topologyId, class CoordType, unsigned coerceToId>
    struct Traits<
      topologyId, CoordType, coerceToId, 3,
      typename enable_if<
        (GenericGeometry::PrismTopology<3>::type::id >> 1) ==
          (topologyId >> 1) &&
        (GenericGeometry::SimplexTopology<3>::type::id >> 1) ==
          (coerceToId >> 1)
        >::type>
    {
      typedef PrismTriangulation::RefinementImp<3, CoordType> Imp;
    };
#endif

  } // namespace RefinementImp

} // namespace Dune

#endif //DUNE_GRID_COMMON_REFINEMENT_PRISMTRIANGULATION_CC
