// Copyright (C) 2007-2021  CEA/DEN, EDF R&D, OPEN CASCADE
//
// Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
//
// 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 2.1 of the License, or (at your option) any later version.
//
// This library 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 library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
//
// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
//
//  File   : SMDS_ElementFactory.hxx
//  Module : SMESH
//
#ifndef _SMDS_ElementFactory_HeaderFile
#define _SMDS_ElementFactory_HeaderFile

#include "SMDS_MeshCell.hxx"
#include "SMDS_MeshNode.hxx"
#include "SMDS_Position.hxx"

#include <Utils_SALOME_Exception.hxx>

#include <boost/container/flat_set.hpp>
#include <boost/dynamic_bitset.hpp>
#include <boost/make_shared.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/shared_ptr.hpp>

#include <set>

#include <vtkType.h>

#include <smIdType.hxx>

class SMDS_ElementChunk;
class SMDS_Mesh;
class SMDS_MeshCell;
class SMDS_MeshNode;

struct _ChunkCompare {
  bool operator () (const SMDS_ElementChunk* c1, const SMDS_ElementChunk* c2) const;
};
typedef boost::ptr_vector<SMDS_ElementChunk>       TChunkVector;
typedef std::set<SMDS_ElementChunk*,_ChunkCompare> TChunkPtrSet;

//------------------------------------------------------------------------------------
/*!
 * \brief Allocate SMDS_MeshElement's (SMDS_MeshCell's or SMDS_MeshNode's )
 *        and bind some attributes to elements:
 *        element ID, element VTK ID, sub-mesh ID, position on shape.
 *
 * Elements are allocated by chunks, so there are used and non-used elements
 */
class SMDS_ElementFactory
{
protected:
  bool                     myIsNodal;          // what to allocate: nodes or cells
  SMDS_Mesh*               myMesh;
  TChunkVector             myChunks;           // array of chunks of elements
  TChunkPtrSet             myChunksWithUnused; // sorted chunks having unused elements
  std::vector< vtkIdType > myVtkIDs;           // myVtkIDs[ smdsID-1 ] == vtkID
  std::vector< smIdType >  mySmdsIDs;          // mySmdsIDs[ vtkID ] == smdsID - 1
  smIdType                 myNbUsedElements;   // counter of elements

  friend class SMDS_ElementChunk;

public:

  SMDS_ElementFactory( SMDS_Mesh* mesh, const bool isNodal=false );
  virtual ~SMDS_ElementFactory();

  //! Return minimal ID of a non-used element
  smIdType GetFreeID();

  //! Return maximal ID of an used element
  smIdType GetMaxID();

  //! Return minimal ID of an used element
  smIdType GetMinID();

  //! Return an element by ID. NULL if the element with the given ID is already used
  SMDS_MeshElement* NewElement( const smIdType id );

  //! Return a SMDS_MeshCell by ID. NULL if the cell with the given ID is already used
  SMDS_MeshCell* NewCell( const smIdType id ) { return static_cast<SMDS_MeshCell*>( NewElement( id )); }

  //! Return an used element by ID. NULL if the element with the given ID is not yet used
  const SMDS_MeshElement* FindElement( const smIdType id ) const;

  //! Return a number of used elements
  smIdType NbUsedElements() const { return myNbUsedElements; }

  //! Return an iterator on all element filtered using a given filter.
  //  nbElemsToReturn is used to optimize by stopping the iteration as soon as
  //  all elements satisfying filtering condition encountered.
  template< class ElemIterator >
  boost::shared_ptr< ElemIterator > GetIterator( SMDS_MeshElement::Filter* filter,
                                                 size_t nbElemsToReturn = -1 );

  //! Return an iterator on all element assigned to a given shape.
  //  nbElemsToReturn is used to optimize by stopping the iteration as soon as
  //  all elements assigned to the shape encountered.
  //  sm1stElem is used to quickly find the first chunk holding elements of the shape;
  //  it must have smallest ID between elements on the shape
  template< class ElemIterator >
  boost::shared_ptr< ElemIterator > GetShapeIterator( int                     shapeID,
                                                      size_t                  nbElemsToReturn,
                                                      const SMDS_MeshElement* sm1stElem );

  //! Mark the element as non-used
  void Free( const SMDS_MeshElement* );

  //! Return an SMDS ID by a Vtk one
  smIdType FromVtkToSmds( vtkIdType vtkID );

  //! De-allocate all elements
  virtual void Clear();

  //! Remove unused elements located not at the end of the last chunk.
  //  Minimize allocated memory
  virtual void Compact(std::vector<smIdType>& idCellsOldToNew);

  //! Return true if Compact() will change IDs of elements
  virtual bool CompactChangePointers();

  //! Return a number of elements in a chunk
  static int ChunkSize();
};

//------------------------------------------------------------------------------------
/*!
 * \brief Allocate SMDS_MeshNode's
 */
class SMDS_NodeFactory : public SMDS_ElementFactory
{
  std::vector<char> myShapeDim; // dimension of shapes

public:

  SMDS_NodeFactory( SMDS_Mesh* mesh );
  ~SMDS_NodeFactory();

  //! Return a SMDS_MeshNode by ID. NULL if the node with the given ID is already used
  SMDS_MeshNode* NewNode( smIdType id ) { return (SMDS_MeshNode*) NewElement(id); }

  //! Return an used node by ID. NULL if the node with the given ID is not yet used
  const SMDS_MeshNode* FindNode( smIdType id ) { return (const SMDS_MeshNode*) FindElement(id); }

  //! Set a total number of sub-shapes in the main shape
  void SetNbShapes( size_t nbShapes );

  //! Return a dimension of a shape
  int  GetShapeDim( int shapeID ) const;

  //! Set a dimension of a shape
  void SetShapeDim( int shapeID, int dim );

  //! De-allocate all nodes
  virtual void Clear();

  //! Remove unused nodes located not at the end of the last chunk.
  //  Minimize allocated memory
  virtual void Compact(std::vector<smIdType>& idNodesOldToNew);

  //! Return true if Compact() will change IDs of node
  virtual bool CompactChangePointers();
};

//------------------------------------------------------------------------------------
/*!
 * \brief Range of elements in a chunk having the same attribute value
 */
template< typename ATTR>
struct _Range
{
  typedef ATTR attr_t;

  attr_t myValue; // common attribute value
  int    my1st;   // index in the chunk of the 1st element 
  _Range( int i0 = 0, attr_t v = 0 ): myValue( v ), my1st( i0 ) {}

  bool operator < (const _Range& other) const { return my1st < other.my1st; }
};

typedef std::vector< std::pair< int, int > > TIndexRanges;

//------------------------------------------------------------------------------------
/*!
 * \brief Sorted set of ranges
 */
template< class RANGE >
struct _RangeSet
{
  typedef typename RANGE::attr_t              attr_t;
  typedef boost::container::flat_set< RANGE > set_t;
  typedef typename set_t::const_iterator      set_iterator;

  set_t mySet;

  _RangeSet() { mySet.insert( RANGE( 0, 0 )); }

  /*!
   * \brief Return a number of ranges
   */
  size_t Size() const { return mySet.size(); }

  /*!
   * \brief Return a mutable _Range::my1st of a range pointed by an iterator
   */
  int&   First( set_iterator rangePtr ) { return const_cast< int& >( rangePtr->my1st ); }

  /*!
   * \brief Return a number of elements in a range pointed by an iterator
   */
  size_t Size( set_iterator rangePtr ) const
  {
    int next1st =
      ( rangePtr + 1 == mySet.end() ) ? SMDS_ElementFactory::ChunkSize() : ( rangePtr + 1 )->my1st;
    return next1st - rangePtr->my1st;
  }

  /*!
   * \brief Return ranges of indices (from,to) of elements having a given value
   */
  bool GetIndices( const attr_t theValue, TIndexRanges & theIndices,
                   const attr_t* /*theMinValue*/ = 0, const attr_t* /*theMaxValue*/ = 0) const
  {
    bool isFound = false;

    // if ( sizeof( attr_t ) == sizeof( int ) && theMinValue )
    //   if ( theValue < *theMinValue || theValue > *theMaxValue )
    //     return isFound;

    for ( set_iterator it = mySet.begin(); it < mySet.end(); ++it )
    {
      if ( it->myValue == theValue )
      {
        theIndices.push_back( std::make_pair( it->my1st, it->my1st + Size( it )));
        isFound = true;
        ++it; // the next range value differs from theValue
      }
    }
    return isFound;
  }

  /*!
   * \brief Return value of an element attribute
   *  \param [in] theIndex - element index
   *  \return attr_t - attribute value
   */
  attr_t GetValue( int theIndex ) const
  {
    set_iterator r = mySet.upper_bound( theIndex ) - 1;
    return r->myValue;
  }

  /*!
   * \brief Change value of an element attribute
   *  \param [in] theIndex - element index
   *  \param [in] theValue - attribute value
   *  \return attr_t - previous value
   */
  attr_t SetValue( int theIndex, attr_t theValue )
  {
    set_iterator rNext = mySet.end(); // case of adding elements
    set_iterator     r = rNext - 1;
    if ( r->my1st > theIndex )
    {
      rNext = mySet.upper_bound( theIndex );
      r     = rNext - 1;
    }
    int         rSize = Size( r ); // range size
    attr_t      rValue = r->myValue;
    if ( rValue == theValue )
      return rValue; // it happens while compacting

    if ( r->my1st == theIndex ) // theIndex is the first in the range
    {
      bool joinPrev = // can join theIndex to the previous range
        ( r->my1st > 0 && ( r-1 )->myValue == theValue );

      if ( rSize == 1 )
      {
        bool joinNext = // can join to the next range
          ( rNext != mySet.end() && rNext->myValue == theValue );

        if ( joinPrev )
        {
          if ( joinNext ) // && joinPrev
          {
            mySet.erase( r, r + 2 );
          }
          else // joinPrev && !joinNext
          {
            mySet.erase( r );
          }
        }
        else
        {
          if ( joinNext ) // && !joinPrev
          {
            r = mySet.erase( r ); // then r points to the next range
            First( r )--;
          }
          else // !joinPrev && !joinNext
          {
            const_cast< attr_t & >( r->myValue ) = theValue;
          }
        }
      }
      else // if rSize > 1
      {
        if ( joinPrev )
        {
          First( r )++;
        }
        else
        {
          r = mySet.insert( r, RANGE( theIndex + 1, rValue )) - 1;
          const_cast< attr_t & >( r->myValue ) = theValue;
        }
      }
    }
    else if ( r->my1st + rSize - 1 == theIndex ) // theIndex is last in the range
    {
      if ( rNext != mySet.end() && rNext->myValue == theValue ) // join to the next
      {
        First( rNext )--;
      }
      else
      {
        mySet.insert( r, RANGE( theIndex, theValue ));
      }
    }
    else // theIndex in the middle of the range
    {
      r = mySet.insert( r, RANGE( theIndex,     theValue ));
      r = mySet.insert( r, RANGE( theIndex + 1, rValue ));
    }
    return rValue;
  }
}; // struct _RangeSet


typedef _Range< int >  _ShapeIDRange; // sub-mesh ID range
typedef _Range< bool > _UsedRange;    // range of used elements

typedef _RangeSet< _ShapeIDRange > TSubIDRangeSet;
typedef _RangeSet< _UsedRange >    TUsedRangeSet;
typedef boost::dynamic_bitset<>    TBitSet;
//typedef float                       TParam;
typedef double                     TParam;
//typedef std::unordered_set<int>    TSubIDSet;

//------------------------------------------------------------------------------------
/*!
 * \brief Allocate SMDS_MeshElement's (SMDS_MeshCell's or SMDS_MeshNode's )
 *        and bind some attributes to elements:
 *        element ID, sub-shape ID, isMarked flag, parameters on shape
 */
class SMDS_ElementChunk
{
  SMDS_ElementFactory* myFactory;     // holder of this chunk
  // SMDS_MeshElement*    myElements;    // array of elements
  std::vector<SMDS_MeshNode> myNodeElements; // array of node elements
  std::vector<SMDS_MeshCell> myCellElements; // array of cell elements

  smIdType             my1stID;       // ID of myElements[0]
  TBitSet              myMarkedSet;   // mark some elements
  TUsedRangeSet        myUsedRanges;  // ranges of used/unused elements
  TSubIDRangeSet       mySubIDRanges; // ranges of elements on the same sub-shape
  //TSubIDSet*           mySubIDSet;    // set of sub-shape IDs
  // int                  myMinSubID;    // min sub-shape ID
  // int                  myMaxSubID;    // max sub-shape ID
  std::vector<TParam>  myPositions;   // UV parameters on shape: 2*param_t per an element

public:

  SMDS_ElementChunk( SMDS_ElementFactory* factory = 0, smIdType id0 = 0 );
  ~SMDS_ElementChunk();

  //! Return an element by an index [0,ChunkSize()]
  // SMDS_MeshElement* Element(int index) { return & myElements[index]; }
  SMDS_MeshElement* Element(int index)
  {
    if (!myNodeElements.empty())
      return &myNodeElements[index];
    else
      return &myCellElements[index];
  }

  //! Return an element by an index [0,ChunkSize()]
  // const SMDS_MeshElement* Element(int index) const { return & myElements[index]; }
  const SMDS_MeshElement* Element(int index) const
  {
    if (!myNodeElements.empty())
      return &myNodeElements[index];
    else
      return &myCellElements[index];
  }


  //! Return ID of the first non-used element
  smIdType  GetUnusedID() const;

  //! Mark an element as used
  void UseElement( const int index );

  //! Mark an element as non-used
  void Free( const SMDS_MeshElement* e );

  //! Check if a given range holds used or non-used elements
  static bool IsUsed( const _UsedRange& r ) { return r.myValue; }

  //! Return index of an element in the chunk
  // int Index( const SMDS_MeshElement* e ) const { return (int)( e - myElements ); }
  int Index( const SMDS_MeshElement* e ) const
  {
    if (!myNodeElements.empty())
      return (int)( static_cast<const SMDS_MeshNode*>(e) - myNodeElements.data() );
    else
      return (int)( static_cast<const SMDS_MeshCell*>(e) - myCellElements.data() );
  }

  //! Return ID of the 1st element in the chunk
  smIdType Get1stID() const { return my1stID; }

  //! Return pointer to on-shape-parameters of a node
  TParam* GetPositionPtr( const SMDS_MeshElement* node, bool allocate=false );

  //! Return ranges of used/non-used elements
  const TUsedRangeSet&  GetUsedRanges() const { return myUsedRanges; }
  const TUsedRangeSet&  GetUsedRangesMinMax( bool& min, bool& max ) const
  { min = false; max = true; return myUsedRanges; }

  //! Return ranges of elements assigned to sub-shapes and min/max of sub-shape IDs
  const TSubIDRangeSet& GetSubIDRangesMinMax( int& /*min*/, int& /*max*/ ) const
  { /*min = myMinSubID; max = myMaxSubID;*/ return mySubIDRanges; }

  //! Minimize allocated memory
  void Compact();

  //! Print some data
  void Dump() const; // debug


  // Methods called by SMDS_MeshElement

  smIdType  GetID( const SMDS_MeshElement* e ) const;

  vtkIdType  GetVtkID( const SMDS_MeshElement* e ) const;
  void SetVTKID( const SMDS_MeshElement* e, const vtkIdType id );

  int  GetShapeID( const SMDS_MeshElement* e ) const;
  void SetShapeID( const SMDS_MeshElement* e, int shapeID ) const;

  bool IsMarked   ( const SMDS_MeshElement* e ) const;
  void SetIsMarked( const SMDS_MeshElement* e, bool is );

  SMDS_PositionPtr GetPosition( const SMDS_MeshNode* n ) const;
  void SetPosition( const SMDS_MeshNode* n, const SMDS_PositionPtr& pos, int shapeID );

  SMDS_Mesh* GetMesh() { return myFactory->myMesh; }
};

//------------------------------------------------------------------------------------
/*!
 * \brief Iterator on elements in chunks
 */
template< class ELEM_ITERATOR, class RANGE_SET >
struct _ChunkIterator : public ELEM_ITERATOR
{
  typedef typename ELEM_ITERATOR::value_type    element_type;
  typedef SMDS_MeshElement::Filter*             filter_ptr;
  typedef typename RANGE_SET::attr_t            attr_type;
  typedef const RANGE_SET& (SMDS_ElementChunk::*get_rangeset_fun)(attr_type&, attr_type&) const;

  const SMDS_MeshElement* myElement;
  TIndexRanges            myRanges;
  int                     myRangeIndex;
  const TChunkVector&     myChunks;
  int                     myChunkIndex;
  get_rangeset_fun        myGetRangeSetFun;
  attr_type               myValue;
  attr_type               myMinValue;
  attr_type               myMaxValue;
  filter_ptr              myFilter;
  size_t                  myNbElemsToReturn;
  size_t                  myNbReturned;

  _ChunkIterator( const TChunkVector &      theChunks,
                  get_rangeset_fun          theGetRangeSetFun,
                  attr_type                 theAttrValue,
                  SMDS_MeshElement::Filter* theFilter,
                  size_t                    theNbElemsToReturn = -1,
                  int                       theChunkIndex = 0):
    myElement( 0 ),
    myRangeIndex( 0 ),
    myChunks( theChunks ),
    myChunkIndex( theChunkIndex-1 ),
    myGetRangeSetFun( theGetRangeSetFun ),
    myValue( theAttrValue ),
    myFilter( theFilter ),
    myNbElemsToReturn( theNbElemsToReturn ),
    myNbReturned( 0 )
  {
    next();
  }
  ~_ChunkIterator()
  {
    delete myFilter;
  }

  virtual bool more()
  {
    return myElement;
  }

  virtual element_type next()
  {
    element_type result = (element_type) myElement;
    myNbReturned += bool( result );

    myElement = 0;
    if ( myNbReturned < myNbElemsToReturn )
      while ( ! nextInRange() )
      {
        if ( ++myRangeIndex >= (int)myRanges.size() )
        {
          myRanges.clear();
          myRangeIndex = 0;
          while ( ++myChunkIndex < (int)myChunks.size() &&
                  !getRangeSet().GetIndices( myValue, myRanges, &myMinValue, &myMaxValue ))
            ;
          if ( myChunkIndex >= (int)myChunks.size() )
            break;
        }
      }
    return result;
  }

  bool nextInRange()
  {
    if ( myRangeIndex < (int)myRanges.size() )
    {
      std::pair< int, int > & range = myRanges[ myRangeIndex ];
      while ( range.first < range.second && !myElement )
      {
        myElement = myChunks[ myChunkIndex ].Element( range.first++ );
        if ( !(*myFilter)( myElement ))
          myElement = 0;
      }
    }
    return myElement;
  }

  const RANGE_SET& getRangeSet()
  {
    return ( myChunks[  myChunkIndex ].*myGetRangeSetFun )( myMinValue, myMaxValue );
  }
}; // struct _ChunkIterator


template< class ElemIterator >
boost::shared_ptr< ElemIterator >
SMDS_ElementFactory::GetIterator( SMDS_MeshElement::Filter* filter,
                                  size_t                    nbElemsToReturn )
{
  typedef _ChunkIterator< ElemIterator, TUsedRangeSet > TChuckIterator;
  return boost::make_shared< TChuckIterator >( myChunks,
                                               & SMDS_ElementChunk::GetUsedRangesMinMax,
                                               /*isUsed=*/true,
                                               filter,
                                               nbElemsToReturn );
}

template< class ElemIterator >
boost::shared_ptr< ElemIterator >
SMDS_ElementFactory::GetShapeIterator( int                     shapeID,
                                       size_t                  nbElemsToReturn,
                                       const SMDS_MeshElement* sm1stElem )
{
  smIdType iChunk = sm1stElem ? (( sm1stElem->GetID() - 1 ) / ChunkSize()) : 0;
  typedef _ChunkIterator< ElemIterator, TSubIDRangeSet > TChuckIterator;
  return boost::make_shared< TChuckIterator >( myChunks,
                                               & SMDS_ElementChunk::GetSubIDRangesMinMax,
                                               /*shapeID=*/shapeID,
                                               new SMDS_MeshElement::NonNullFilter(),
                                               nbElemsToReturn,
                                               iChunk );
}

#endif
