// Copyright (C) 2007-2022  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   : StdMeshers_BlockRenumber.cxx
//  Author : Edward AGAPOV, OCC
//  Module : SMESH

#include "StdMeshers_BlockRenumber.hxx"

#include <SMDS_EdgePosition.hxx>
#include <SMDS_FacePosition.hxx>
#include <SMESHDS_Mesh.hxx>
#include <SMESHDS_SubMesh.hxx>
#include <SMESH_Algo.hxx>
#include <SMESH_Mesh.hxx>
#include <SMESH_MesherHelper.hxx>
#include <SMESH_TryCatch.hxx>
#include <SMESH_BoostTxtArchive.hxx>

#include <BRep_Tool.hxx>
#include <TopExp_Explorer.hxx>
#include <TopTools_MapOfShape.hxx>
#include <TopoDS.hxx>

#include <boost/archive/text_oarchive.hpp>

//=============================================================================
/*!
 * Constructor 
 */
//=============================================================================

StdMeshers_BlockRenumber::StdMeshers_BlockRenumber(int hypId, SMESH_Gen * gen)
  :SMESH_Hypothesis(hypId, gen)
{
  _name = "BlockRenumber";
  _param_algo_dim = 3; // is used by StdMeshers_Hexa_3D and StdMeshers_CompositeHexa_3D
}

//================================================================================
/*!
 * \brief Set local CS of blocks
 */
//================================================================================

void StdMeshers_BlockRenumber::SetBlocksOrientation( std::vector< StdMeshers_BlockCS > & blockCS )
{
  if ( _blockCS != blockCS )
  {
    NotifySubMeshesHypothesisModification();
    _blockCS.swap( blockCS );
    _solids2vertices.Clear();
  }
}

//================================================================================
/*
 * Return true and vertices if block orientation is defined for a given solid
 */
//================================================================================

bool StdMeshers_BlockRenumber::IsSolidIncluded( SMESH_Mesh&         mesh,
                                                const TopoDS_Shape& solid,
                                                TopoDS_Vertex&      vertex000,
                                                TopoDS_Vertex&      vertex001 ) const
{
  bool result = false;
  vertex000.Nullify();
  vertex001.Nullify();

  if ( _solids2vertices.IsEmpty() )
  {
    StdMeshers_BlockRenumber* me = const_cast<StdMeshers_BlockRenumber*>(this);
    for ( StdMeshers_BlockCS& bcs : me->_blockCS )
    {
      TopoDS_Shape so   = mesh.GetShapeByEntry( bcs._solid );
      TopoDS_Shape s000 = mesh.GetShapeByEntry( bcs._vertex000 );
      TopoDS_Shape s001 = mesh.GetShapeByEntry( bcs._vertex001 );
      TopoDS_Vertex v000 = StdMeshers_RenumberHelper::GetVertexAtPoint( so, s000 );
      TopoDS_Vertex v001 = StdMeshers_RenumberHelper::GetVertexAtPoint( so, s001 );
      if ( !v000.IsNull() && !v001.IsNull() )
      {
        me->_solids2vertices.Bind( so, std::make_pair( v000, v001 ));
        if ( so.IsSame( solid ))
        {
          result = true;
          vertex000 = v000;
          vertex001 = v001;
        }
      }
    }
  }
  else if ( !solid.IsNull() )
  {
    if (( result = _solids2vertices.IsBound( solid )))
    {
      auto vvPairPtr = _solids2vertices.Seek( solid );
      vertex000 = vvPairPtr->first;
      vertex001 = vvPairPtr->second;
    }
  }
  return result;
}

//=======================================================================
//function : CheckHypothesis
//purpose  : 
//=======================================================================

SMESH_ComputeErrorPtr StdMeshers_BlockRenumber::CheckHypothesis(SMESH_Mesh&         aMesh,
                                                                const TopoDS_Shape& /*shape*/) const
{
  SMESH_Comment errorTxt;
  for ( size_t i = 0; i < _blockCS.size() &&  errorTxt.empty(); ++i )
  {
    TopoDS_Shape solid = aMesh.GetShapeByEntry( _blockCS[i]._solid );
    TopoDS_Shape  v000 = aMesh.GetShapeByEntry( _blockCS[i]._vertex000 );
    TopoDS_Shape  v001 = aMesh.GetShapeByEntry( _blockCS[i]._vertex001 );
    v000 = StdMeshers_RenumberHelper::GetVertexAtPoint( solid, v000 );
    v001 = StdMeshers_RenumberHelper::GetVertexAtPoint( solid, v001 );

    if ( solid.IsNull() || solid.ShapeType() != TopAbs_SOLID )
      errorTxt << "Can't find a SOLID by entry '" << _blockCS[i]._solid << "'";
    else if ( v000.IsNull() || v000.ShapeType() != TopAbs_VERTEX )
      errorTxt << "Can't find a VERTEX by entry '" << _blockCS[i]._vertex000 << "'";
    else if ( v001.IsNull() || v001.ShapeType() != TopAbs_VERTEX )
      errorTxt << "Can't find a VERTEX by entry '" << _blockCS[i]._vertex001 << "'";
    else if ( !SMESH_MesherHelper::IsSubShape( v000, solid ))
      errorTxt << "VERTEX '" << _blockCS[i]._vertex001 << "' does not belong to SOLID '"
               << _blockCS[i]._solid << "'";
    else if ( !SMESH_MesherHelper::IsSubShape( v001, solid ))
      errorTxt << "VERTEX '" << _blockCS[i]._vertex001 << "' does not belong to SOLID '"
               << _blockCS[i]._solid << "'";
    else if ( SMESH_MesherHelper::Count( solid, TopAbs_VERTEX, true ) == 8 &&
              SMESH_MesherHelper::GetCommonAncestor( v000, v001, aMesh, TopAbs_EDGE ).IsNull() )
      errorTxt << "Vertices '" << _blockCS[i]._vertex000 << "' and '" << _blockCS[i]._vertex001
               << "' are not connected by an edge";
  }

  SMESH_ComputeErrorPtr error;
  if ( !errorTxt.empty() )
  {
    error = SMESH_ComputeError::New( COMPERR_BAD_PARMETERS,
                                     SMESH_Comment("Renumber hypothesis: ") << errorTxt );
  }
  return error;
}

//=======================================================================
//function : StdMeshers_RenumberHelper
//purpose  : constructor
//=======================================================================

StdMeshers_RenumberHelper::StdMeshers_RenumberHelper( SMESH_Mesh&                     mesh,
                                                      const StdMeshers_BlockRenumber* hyp)
  : _mesh( &mesh ), _hyp( hyp ), _newOldNodes( 2, nullptr )
{
}

//=======================================================================
//function : GetVertex000
//purpose  : Find default vertex at (0,0,0) local position
//=======================================================================

TopoDS_Vertex StdMeshers_RenumberHelper::GetVertex000( const TopTools_MapOfShape& cornerVertices )
{
  TopoDS_Vertex v000;
  if ( cornerVertices.Extent() < 8 )
    return TopoDS_Vertex();

  double minVal = DBL_MAX, minX = DBL_MAX, val;
  for ( auto it = cornerVertices.cbegin(); it != cornerVertices.cend(); ++it )
  {
    gp_Pnt P = BRep_Tool::Pnt( TopoDS::Vertex( *it ));
    val = P.X() + P.Y() + P.Z();
    if ( val < minVal || ( val == minVal && P.X() < minX ))
    {
      v000 = TopoDS::Vertex( *it );
      minVal = val;
      minX = P.X();
    }
  }
  return v000;
}

//=======================================================================
//function : GetVertexAtPoint
//purpose  : Return the VERTEX of solid at given point
//=======================================================================

TopoDS_Vertex StdMeshers_RenumberHelper::GetVertexAtPoint( const TopoDS_Shape& solid,
                                                           const TopoDS_Shape& point )
{
  if ( !solid.IsNull() && !point.IsNull() && point.ShapeType() == TopAbs_VERTEX )
  {
    gp_Pnt   p = BRep_Tool::Pnt( TopoDS::Vertex( point ));
    double tol = Precision::Confusion();
    for ( TopExp_Explorer exp( solid, TopAbs_VERTEX ); exp.More(); exp.Next() )
    {
      const TopoDS_Vertex& v = TopoDS::Vertex( exp.Current() );
      if ( v.IsSame( point ) || p.IsEqual( BRep_Tool::Pnt( v ), tol ))
        return v;
    }
  }
  return TopoDS_Vertex();
}

//================================================================================
/*
 * Create a copy of an old node and remember this couple of nodes for replacement
 */
//================================================================================

void StdMeshers_RenumberHelper::AddReplacingNode( const SMDS_MeshNode* & oldNode )
{
  SMESHDS_Mesh*     mesh = _mesh->GetMeshDS();
  SMESH_NodeXYZ   oldXYZ = oldNode;
  SMDS_MeshNode* newNode = mesh->AddNode( oldXYZ.X(), oldXYZ.Y(), oldXYZ.Z() );
  _newOldNodes.front() = newNode;
  _newOldNodes.back()  = oldNode;
  _nodesToMerge.push_back( _newOldNodes );
  oldNode = newNode;

  int               shapeID = oldXYZ->GetShapeID();
  const TopoDS_Shape& shape = mesh->IndexToShape( shapeID );
  if ( !shape.IsNull() )
    switch ( shape.ShapeType() )
    {
    case TopAbs_FACE:
      if ( SMDS_FacePositionPtr pos = oldXYZ->GetPosition() )
        mesh->SetNodeOnFace( newNode, shapeID, pos->GetUParameter(), pos->GetVParameter() );
      break;
    case TopAbs_EDGE:
      if ( SMDS_EdgePositionPtr pos = oldXYZ->GetPosition() )
        mesh->SetNodeOnEdge( newNode, shapeID, pos->GetUParameter() );
      break;
    case TopAbs_VERTEX:
      mesh->SetNodeOnVertex( newNode, shapeID );
      break;
    default:
      mesh->SetNodeInVolume( newNode, shapeID );
    }
}

//================================================================================
/*
 * Replace old nodes by new ones
 */
//================================================================================

void StdMeshers_RenumberHelper::DoReplaceNodes()
{
  SMESH_MeshEditor( _mesh ).MergeNodes( _nodesToMerge );
}

//=============================================================================
/*!
 * Persistence
 */
//=============================================================================

ostream & StdMeshers_BlockRenumber::SaveTo(ostream & save)
{
  boost::archive::text_oarchive archive( save );
  archive << *this;

  return save;
}

//=============================================================================
/*!
 * Persistence
 */
//=============================================================================

istream & StdMeshers_BlockRenumber::LoadFrom(istream & load)
{
  SMESH_TRY;

  SMESHUtils::BoostTxtArchive( load ) >> *this;

  SMESH_CATCH( SMESH::doNothing );

  return load;
}

namespace boost {
  namespace serialization {

    //=======================================================================
    //function : serialize
    //purpose  : serialize StdMeshers_BlockCS
    //=======================================================================

    template<class Archive>
    void serialize(Archive & ar, StdMeshers_BlockCS & blockCS, const unsigned int /*version*/)
    {
      ar & blockCS._solid;
      ar & blockCS._vertex000;
      ar & blockCS._vertex001;
    }

  } // namespace serialization
} // namespace boost
