// Copyright (C) 2007-2012  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.
//
// 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   : SMESHDS_GroupOnFilter.cxx
//  Module : SMESH
//
#include "SMESHDS_GroupOnFilter.hxx"

#include "SMESHDS_Mesh.hxx"
#include "SMDS_SetIterator.hxx"

#include <numeric>
#include <limits>

using namespace std;

//=============================================================================
/*!
 * Creates a group based on thePredicate
 */
//=============================================================================

SMESHDS_GroupOnFilter::SMESHDS_GroupOnFilter (const int                 theID,
                                              const SMESHDS_Mesh*       theMesh,
                                              const SMDSAbs_ElementType theType,
                                              const SMESH_PredicatePtr& thePredicate)
  : SMESHDS_GroupBase(theID,theMesh,theType),
    myMeshInfo( SMDSEntity_Last, 0 ),
    myMeshModifTime(0),
    myPredicateTic(0),
    myNbElemToSkip(0)
{
  SetPredicate( thePredicate );
}

//================================================================================
/*!
 * \brief Sets a new predicate
 */
//================================================================================

void SMESHDS_GroupOnFilter::SetPredicate( const SMESH_PredicatePtr& thePredicate )
{
  myPredicate = thePredicate;
  ++myPredicateTic;
  setChanged();
  if ( myPredicate )
    myPredicate->SetMesh( GetMesh() );
}

//================================================================================
/*!
 * \brief Returns nb of elements
 */
//================================================================================

int SMESHDS_GroupOnFilter::Extent() const
{
  update();
  return std::accumulate( myMeshInfo.begin(), myMeshInfo.end(), 0 );
}

//================================================================================
/*!
 * \brief Checks emptyness
 */
//================================================================================

bool SMESHDS_GroupOnFilter::IsEmpty()
{
  if ( IsUpToDate() )
  {
    return ( Extent() == 0 );
  }
  else // not up-to-date
  {
    setChanged();
    SMDS_ElemIteratorPtr okElemIt = GetElements();
    if ( !okElemIt->more() )
    {
      // no satisfying elements
      setChanged( false );
    }
    else
    {
      return false;
    }
  }
  return true;
}

//================================================================================
/*!
 * \brief Checks if the element belongs to the group
 */
//================================================================================

bool SMESHDS_GroupOnFilter::Contains (const int theID)
{
  return myPredicate ? myPredicate->IsSatisfy( theID ) : false;
}

//================================================================================
/*!
 * \brief Checks if the element belongs to the group
 */
//================================================================================

bool SMESHDS_GroupOnFilter::Contains (const SMDS_MeshElement* elem)
{
  return myPredicate ? myPredicate->IsSatisfy( elem->GetID() ) : false;
}

//================================================================================
namespace // Iterator
{
  struct TIterator : public SMDS_ElemIterator
  {
    SMESH_PredicatePtr      myPredicate;
    SMDS_ElemIteratorPtr    myElemIt;
    const SMDS_MeshElement* myNextElem;
    size_t                  myNbToFind, myNbFound;
    TIterator( const SMESH_PredicatePtr& filter,
               SMDS_ElemIteratorPtr&     elems,
               size_t                    nbToFind):
      myPredicate( filter ),
      myElemIt( elems ),
      myNextElem( 0 ),
      myNbToFind( nbToFind ),
      myNbFound( 0 )
    {
      next();
    }
    virtual bool more()
    {
      return myNextElem;
    }
    virtual const SMDS_MeshElement* next()
    {
      const SMDS_MeshElement* res = myNextElem;
      myNbFound += bool( res );
      myNextElem = 0;
      if ( myNbFound < myNbToFind )
        while ( myElemIt->more() && !myNextElem )
        {
          myNextElem = myElemIt->next();
          if ( !myPredicate->IsSatisfy( myNextElem->GetID() ))
            myNextElem = 0;
        }
      return res;
    }
  };

  struct TEmptyIterator : public SMDS_ElemIterator
  {
    virtual bool more()                    { return false; }
    virtual const SMDS_MeshElement* next() { return 0; }
  };
}

//================================================================================
/*!
 * \brief Return iterator on all elements
 */
//================================================================================

SMDS_ElemIteratorPtr SMESHDS_GroupOnFilter::GetElements() const
{
  size_t nbToFind = std::numeric_limits<size_t>::max();

  SMDS_ElemIteratorPtr elemIt;
  if ( myPredicate )
  {
    myPredicate->SetMesh( GetMesh() ); // hope myPredicate updates self here if necessary

    elemIt = GetMesh()->elementsIterator( GetType() );
    if ( IsUpToDate() )
    {
      nbToFind = Extent();
      if ( nbToFind == GetMesh()->GetMeshInfo().NbElements( GetType() ))
        return elemIt; // all elements are OK
      for ( size_t i = 0; i < myNbElemToSkip; ++i )
        elemIt->next(); // skip w/o check
    }
  }
  else
  {
    elemIt = SMDS_ElemIteratorPtr( new TEmptyIterator );
  }
  return SMDS_ElemIteratorPtr ( new TIterator( myPredicate, elemIt, nbToFind ));
}

//================================================================================
/*!
 * \brief Return info on sub-types of elements
 */
//================================================================================

std::vector< int > SMESHDS_GroupOnFilter::GetMeshInfo() const
{
  update();
  return myMeshInfo;
}

//================================================================================
/*!
 * \brief Fill ids of elements. And return their number.
 *       \a ids must be pre-allocated using nb of elements of type == GetType()
 */
//================================================================================

int SMESHDS_GroupOnFilter::GetElementIds( int* ids ) const
{
  SMESHDS_GroupOnFilter* me = const_cast<SMESHDS_GroupOnFilter*>( this );

  int* curID = ids;
  SMDS_ElemIteratorPtr elIt = GetElements();
  if ( elIt->more() )
  {
    if ( IsUpToDate() )
    {
      while ( elIt->more() )
        *curID++ = elIt->next()->GetID();
    }
    else
    {
      me->setChanged();

      // find out nb of elements to skip w/o check before the 1st OK element
      const SMDS_MeshElement* firstOkElem = me->setNbElemToSkip( elIt );

      me->myMeshInfo.assign( SMDSEntity_Last, 0 );
      me->myMeshInfo[ firstOkElem->GetEntityType() ]++;
      *curID++ = firstOkElem->GetID();
      while ( elIt->more() )
      {
        const SMDS_MeshElement* e = elIt->next();
        me->myMeshInfo[ e->GetEntityType() ]++;
        *curID++ = e->GetID();
      }
    }
  }
  me->setChanged( false );

  return curID - ids;
}

//================================================================================
/*!
 * \brief Return a value allowing to find out if a group has changed or not
 */
//================================================================================

int SMESHDS_GroupOnFilter::GetTic() const
{
  return GetMesh()->GetMTime() * myPredicateTic;
}

//================================================================================
/*!
 * \brief Return false if update() is needed
 */
//================================================================================

bool SMESHDS_GroupOnFilter::IsUpToDate() const
{
  return !( myMeshModifTime < GetMesh()->GetMTime() );
}

//================================================================================
/*!
 * \brief Updates myElements if necessary
 */
//================================================================================

void SMESHDS_GroupOnFilter::update() const
{
  SMESHDS_GroupOnFilter* me = const_cast<SMESHDS_GroupOnFilter*>( this );
  if ( !IsUpToDate() )
  {
    me->setChanged();
    SMDS_ElemIteratorPtr elIt = GetElements();
    if ( elIt->more() ) {
      // find out nb of elements to skip w/o check before the 1st OK element
      const SMDS_MeshElement* e = me->setNbElemToSkip( elIt );
      ++me->myMeshInfo[ e->GetEntityType() ];
      while ( elIt->more() )
        ++me->myMeshInfo[ elIt->next()->GetEntityType() ];
    }
    me->setChanged( false );
  }
}

//================================================================================
/*!
 * \brief Sets myMeshModifTime and clear fields according to modification state
 */
//================================================================================

void SMESHDS_GroupOnFilter::setChanged(bool changed)
{
  myMeshModifTime = GetMesh()->GetMTime();
  if ( changed && myMeshModifTime != 0 )
    --myMeshModifTime;
  if ( changed ) {
    myNbElemToSkip = 0;
    myMeshInfo.assign( SMDSEntity_Last, 0 );
  }
}

//================================================================================
/*!
 * \brief Sets myNbElemToSkip
 *  \param okElemIt - iterator on OK elements
 *  \retval const SMDS_MeshElement* - the first OK element
 */
//================================================================================

const SMDS_MeshElement*
SMESHDS_GroupOnFilter::setNbElemToSkip( SMDS_ElemIteratorPtr& okElemIt )
{
  // find out nb of elements to skip w/o check before the 1st OK element
  const SMDS_MeshElement* firstOkElem = okElemIt->next();
  if ( myNbElemToSkip == 0 )
  {
    SMDS_ElemIteratorPtr elemIt = GetMesh()->elementsIterator( GetType() );
    myNbElemToSkip = 0;
    while ( elemIt->next() != firstOkElem )
      ++myNbElemToSkip;
  }
  return firstOkElem;
}
