/***************************************************************************
                          tnlMeshSuperentityStorageInitializer.h  -  description
                             -------------------
    begin                : Feb 27, 2014
    copyright            : (C) 2014 by Tomas Oberhuber
    email                : tomas.oberhuber@fjfi.cvut.cz
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef TNLMESHSUPERENTITYSTORAGEINITIALIZER_H_
#define TNLMESHSUPERENTITYSTORAGEINITIALIZER_H_

#include <mesh/tnlDimensionsTag.h>
#include <algorithm>

template< typename MeshConfig >
class tnlMeshInitializer;

template< typename MeshConfig,
          typename EntityTopology,
          typename DimensionsTag,
          bool SuperentityStorage = tnlMeshSuperentityTraits< MeshConfig, EntityTopology, DimensionsTag::value >::storageEnabled >
class tnlMeshSuperentityStorageInitializerLayer;

template< typename MeshConfig,
          typename EntityTopology >
class tnlMeshSuperentityStorageInitializer :
   public tnlMeshSuperentityStorageInitializerLayer< MeshConfig, EntityTopology, tnlDimensionsTag< tnlMeshTraits< MeshConfig >::meshDimensions > >
{};

template< typename MeshConfig,
          typename EntityTopology,
          typename DimensionsTag >
class tnlMeshSuperentityStorageInitializerLayer< MeshConfig,
                                          EntityTopology,
                                          DimensionsTag,
                                          true >
   : public tnlMeshSuperentityStorageInitializerLayer< MeshConfig,
                                                EntityTopology,
                                                typename DimensionsTag::Decrement >
{
   typedef tnlMeshSuperentityStorageInitializerLayer< MeshConfig,
                                                      EntityTopology,
                                                      typename DimensionsTag::Decrement >      BaseType;

   static const int Dimensions = DimensionsTag::value;
   typedef tnlDimensionsTag< EntityTopology::dimensions >                                    EntityDimensions;
	
   typedef tnlMeshTraits< MeshConfig >                                                      MeshTraits;
   typedef typename MeshTraits::GlobalIdArrayType                                           GlobalIdArrayType;
      
   typedef typename MeshTraits::GlobalIndexType                                             GlobalIndexType;
   typedef typename MeshTraits::LocalIndexType                                              LocalIndexType;
   typedef tnlMeshInitializer< MeshConfig >                                                 MeshInitializer;
   typedef typename MeshTraits::template SuperentityTraits< EntityTopology, Dimensions >    SuperentityTraits;
   typedef typename SuperentityTraits::StorageNetworkType                                   SuperentityStorageNetwork;

   public:      
      using BaseType::addSuperentity;
	   
      void addSuperentity( DimensionsTag, GlobalIndexType entityIndex, GlobalIndexType superentityIndex)
      {
         //cout << "Adding superentity with " << DimensionsTag::value << " dimensions of enity with " << EntityDimensions::value << " ... " << endl;
         indexPairs.push_back( IndexPair{ entityIndex, superentityIndex } );
      }

      using BaseType::initSuperentities;
      void initSuperentities( MeshInitializer& meshInitializer )
      {
         std::sort( indexPairs.begin(),
                    indexPairs.end(),
                    []( IndexPair pair0, IndexPair pair1 ){ return ( pair0.entityIndex < pair1.entityIndex ); } );

         GlobalIdArrayType &superentityIdsArray = meshInitializer.template meshSuperentityIdsArray< EntityDimensions, DimensionsTag >();         
         superentityIdsArray.setSize( static_cast< GlobalIndexType >( indexPairs.size() )  );
         GlobalIndexType currentBegin = 0;
         GlobalIndexType lastEntityIndex = 0;
         cout << "There are " << superentityIdsArray.getSize() << " superentities with " << DimensionsTag::value << " dimensions of enities with " << EntityDimensions::value << " ... " << endl;
         for( GlobalIndexType i = 0; i < superentityIdsArray.getSize(); i++)
         {
            superentityIdsArray[ i ] = indexPairs[i].superentityIndex;
            
            //cout << "Adding superentity " << indexPairs[i].superentityIndex << " to entity " << lastEntityIndex << endl;
            if( indexPairs[ i ].entityIndex != lastEntityIndex )
            {
               meshInitializer.template superentityIdsArray< DimensionsTag >( meshInitializer.template meshEntitiesArray< EntityDimensions >()[ lastEntityIndex ] ).bind( superentityIdsArray, currentBegin, i - currentBegin );
               currentBegin = i;
               lastEntityIndex = indexPairs[ i ].entityIndex;
            }
         }

         meshInitializer.template superentityIdsArray< DimensionsTag >( meshInitializer.template meshEntitiesArray< EntityDimensions >()[ lastEntityIndex ] ).bind( superentityIdsArray, currentBegin, superentityIdsArray.getSize() - currentBegin );
         indexPairs.clear();
         
         /****
          * Network initializer
          */
         SuperentityStorageNetwork& superentityStorageNetwork = meshInitializer.template meshSuperentityStorageNetwork< EntityTopology, DimensionsTag >();
         //GlobalIndexType lastEntityIndex( 0 );
         superentityStorageNetwork.setRanges(
            meshInitializer.template meshEntitiesArray< EntityDimensions >().getSize(),
            meshInitializer.template meshEntitiesArray< DimensionsTag >().getSize() );
         lastEntityIndex = 0;
         typename SuperentityStorageNetwork::ValuesAllocationVectorType storageNetworkAllocationVector;
         storageNetworkAllocationVector.setSize( meshInitializer.template meshEntitiesArray< EntityDimensions >().getSize() );
         storageNetworkAllocationVector.setValue( 0 );
         for( GlobalIndexType i = 0; i < superentityIdsArray.getSize(); i++)
         {
            if( indexPairs[ i ].entityIndex == lastEntityIndex )
               storageNetworkAllocationVector[ lastEntityIndex ]++;
            else
               lastEntityIndex++;                           
         }
         superentityStorageNetwork.allocate( storageNetworkAllocationVector );
         lastEntityIndex = 0;
         LocalIndexType superentitiesCount( 0 );
         typename SuperentityStorageNetwork::ValuesAccessorType superentitiesIndecis = 
            superentityStorageNetwork.getValues( lastEntityIndex );
         for( GlobalIndexType i = 0; i < superentityIdsArray.getSize(); i++)
         {
            if( indexPairs[ i ].entityIndex != lastEntityIndex )
            {
               superentitiesIndecis = superentityStorageNetwork.getValues( ++lastEntityIndex );
               superentitiesCount = 0;
            }
            superentitiesIndecis[ superentitiesCount++ ] =  indexPairs[ i ].superentityIndex;
         }
         BaseType::initSuperentities( meshInitializer );
      }

   private:
      struct IndexPair
      {
         GlobalIndexType entityIndex;
         GlobalIndexType superentityIndex;
      };

      std::vector< IndexPair > indexPairs;
   
};

template< typename MeshConfig,
          typename EntityTopology,
          typename DimensionsTag >
class tnlMeshSuperentityStorageInitializerLayer< MeshConfig,
                                          EntityTopology,
                                          DimensionsTag,
                                          false >
   : public tnlMeshSuperentityStorageInitializerLayer< MeshConfig,
                                                EntityTopology,
                                                typename DimensionsTag::Decrement >
{
   typedef tnlMeshSuperentityStorageInitializerLayer< MeshConfig,
                                                EntityTopology,
                                                typename DimensionsTag::Decrement > BaseType;
   typedef tnlMeshInitializer< MeshConfig >                                      MeshInitializerType;
   
   public:
   void addSuperentity()                           {} // This method is due to 'using BaseType::...;' in the derived classes.
   using BaseType::initSuperentities;
   void initSuperentities( MeshInitializerType& ) {cerr << "***" << endl;} 
};

template< typename MeshConfig,
          typename EntityTopology >
class tnlMeshSuperentityStorageInitializerLayer< MeshConfig,
                                          EntityTopology,
                                          tnlDimensionsTag< EntityTopology::dimensions >,
                                          true >
{
   typedef tnlMeshInitializer< MeshConfig >                                      MeshInitializerType;
   
   public:
   void addSuperentity()                           {} // This method is due to 'using BaseType::...;' in the derived classes.
   void initSuperentities( MeshInitializerType& ) {}
};

template< typename MeshConfig,
          typename EntityTopology >
class tnlMeshSuperentityStorageInitializerLayer< MeshConfig,
                                          EntityTopology,
                                          tnlDimensionsTag< EntityTopology::dimensions >,
                                          false >
{
   typedef tnlMeshInitializer< MeshConfig >                                      MeshInitializerType;

   public:
   void addSuperentity()                           {} // This method is due to 'using BaseType::...;' in the derived classes.
   void initSuperentities( MeshInitializerType& ) {}
};




#endif /* TNLMESHSUPERENTITYSTORAGEINITIALIZER_H_ */