// Copyright (C) 2007-2015  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:     GEOMAlgo_GetInPlaceAPI.cxx
// Created:
// Author:   Sergey KHROMOV


#include <GEOMAlgo_GetInPlaceAPI.hxx>
#include <GEOMAlgo_GetInPlace.hxx>
#include <GEOM_Function.hxx>
#include <GEOM_Object.hxx>
#include <GEOMUtils.hxx>

#include <Bnd_Box.hxx>
#include <BRepAdaptor_Surface.hxx>
#include <BRepBndLib.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepExtrema_DistShapeShape.hxx>
#include <BRepGProp.hxx>
#include <BRep_Tool.hxx>
#include <Geom2d_Curve.hxx>
#include <GProp_GProps.hxx>
#include <gp_Pnt.hxx>
#include <Precision.hxx>
#include <TDataStd_IntegerArray.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopTools_MapOfShape.hxx>


//=======================================================================
//function : GetInPlace
//purpose  : 
//=======================================================================
Standard_Boolean GEOMAlgo_GetInPlaceAPI::GetInPlace
                      (const TopoDS_Shape        &theWhere,
                       const TopoDS_Shape        &theWhat,
                             GEOMAlgo_GetInPlace &theGIP)
{
  if (theWhere.IsNull() || theWhat.IsNull()) {
    return Standard_False;
  }

  // Compute confusion tolerance.
  Standard_Real    aTolConf = Precision::Confusion();
  Standard_Integer i;

  for (i = 0; i < 2; ++i) {
    TopExp_Explorer anExp(i == 0 ? theWhere : theWhat, TopAbs_VERTEX);

    for (; anExp.More(); anExp.Next()) {
      const TopoDS_Vertex aVtx = TopoDS::Vertex(anExp.Current());
      const Standard_Real aTolVtx = BRep_Tool::Tolerance(aVtx);

      if (aTolVtx > aTolConf) {
        aTolConf = aTolVtx;
      }
    }
  }

  // Compute mass tolerance.
  Bnd_Box       aBoundingBox;
  Standard_Real aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
  Standard_Real aMassTol;

  BRepBndLib::Add(theWhere, aBoundingBox);
  BRepBndLib::Add(theWhat,  aBoundingBox);
  aBoundingBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
  aMassTol = Max(aXmax - aXmin, aYmax - aYmin);
  aMassTol = Max(aMassTol, aZmax - aZmin);
  aMassTol *= aTolConf;

  // Searching for the sub-shapes inside the ShapeWhere shape
  theGIP.SetTolerance(aTolConf);
  theGIP.SetTolMass(aMassTol);
  theGIP.SetTolCG(aTolConf);

  theGIP.SetArgument(theWhat);
  theGIP.SetShapeWhere(theWhere);

  theGIP.Perform();

  int iErr = theGIP.ErrorStatus();

  if (iErr) {
    return Standard_False;
  }

  return Standard_True;
}

//=======================================================================
//function : GetInPlaceOld
//purpose  : 
//=======================================================================
Standard_Integer GEOMAlgo_GetInPlaceAPI::GetInPlaceOld
            (const TopoDS_Shape         &theWhere,
             const TopoDS_Shape         &theWhat,
                   TopTools_ListOfShape &theShapesInPlace)
{
  theShapesInPlace.Clear();

  if (theWhere.IsNull() || theWhat.IsNull()) {
    // Error: aWhere and aWhat TopoDS_Shape are Null.
    return 1;
  }

  TopoDS_Shape     aPntShape;
  TopoDS_Vertex    aVertex;
  bool             isFound = false;
  TopAbs_ShapeEnum iType   = TopAbs_SOLID;
  //Standard_Real    aWhat_Mass = 0., aWhere_Mass = 0.;
  Standard_Real    tab_aWhat[4],    tab_aWhere[4];
  Standard_Real    dl_l = 1e-3;
  Standard_Real    min_l, Tol_0D, Tol_1D, Tol_2D, Tol_3D, Tol_Mass;
  Standard_Real    aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
  Bnd_Box          BoundingBox;
  gp_Pnt           aPnt, aPnt_aWhat, tab_Pnt[2];
  GProp_GProps     aProps;

  iType = GEOMUtils::GetTypeOfSimplePart(theWhat);
  if (iType == TopAbs_SHAPE) {
    // Error: An attempt to extract a shape of not supported type.
    return 2;
  }

  TopExp_Explorer Exp_aWhat  ( theWhat,  iType );
  TopExp_Explorer Exp_aWhere ( theWhere, iType );
  TopExp_Explorer Exp_Edge   ( theWhere, TopAbs_EDGE );

  // Find the shortest edge in theShapeWhere shape
  BRepBndLib::Add(theWhere, BoundingBox);
  BoundingBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
  min_l = fabs(aXmax - aXmin);
  if( min_l < fabs(aYmax - aYmin) ) min_l = fabs(aYmax - aYmin);
  if( min_l < fabs(aZmax - aZmin) ) min_l = fabs(aZmax - aZmin);
  min_l /= dl_l;
  // Mantis issue 0020908 BEGIN
  if (!Exp_Edge.More()) {
    min_l = Precision::Confusion();
  }
  // Mantis issue 0020908 END
  for ( Standard_Integer nbEdge = 0; Exp_Edge.More(); Exp_Edge.Next(), nbEdge++ ) {
    TopExp_Explorer Exp_Vertex( Exp_Edge.Current(), TopAbs_VERTEX);
    for ( Standard_Integer nbVertex = 0; Exp_Vertex.More(); Exp_Vertex.Next(), nbVertex++ ) {
      aPnt = BRep_Tool::Pnt( TopoDS::Vertex( Exp_Vertex.Current() ) );
      tab_Pnt[nbVertex] = aPnt;
    }
    if ( ! tab_Pnt[0].IsEqual(tab_Pnt[1], dl_l) ) {
      BRepGProp::LinearProperties(Exp_Edge.Current(), aProps);
      if ( aProps.Mass() < min_l ) min_l = aProps.Mass();
    }
  }

  // Compute tolerances
  Tol_0D = dl_l;
  Tol_1D = dl_l * min_l;
  Tol_2D = dl_l * ( min_l * min_l) * ( 2. + dl_l);
  Tol_3D = dl_l * ( min_l * min_l * min_l ) * ( 3. + (3 * dl_l) + (dl_l * dl_l) );

  if (Tol_0D < Precision::Confusion()) Tol_0D = Precision::Confusion();
  if (Tol_1D < Precision::Confusion()) Tol_1D = Precision::Confusion();
  if (Tol_2D < Precision::Confusion()) Tol_2D = Precision::Confusion();
  if (Tol_3D < Precision::Confusion()) Tol_3D = Precision::Confusion();

  Tol_Mass = Tol_3D;
  if ( iType == TopAbs_VERTEX )    Tol_Mass = Tol_0D;
  else if ( iType == TopAbs_EDGE ) Tol_Mass = Tol_1D;
  else if ( iType == TopAbs_FACE ) Tol_Mass = Tol_2D;

  // Searching for the sub-shapes inside the ShapeWhere shape
  TopTools_MapOfShape map_aWhere;
  for ( Exp_aWhere.ReInit(); Exp_aWhere.More(); Exp_aWhere.Next() ) {
    if (!map_aWhere.Add(Exp_aWhere.Current()))
      continue; // skip repeated shape to avoid mass addition
    GetShapeProperties( Exp_aWhere.Current(), tab_aWhere, aPnt );
    for ( Exp_aWhat.ReInit(); Exp_aWhat.More(); Exp_aWhat.Next() ) {
      GetShapeProperties( Exp_aWhat.Current(), tab_aWhat, aPnt_aWhat );
      if ( fabs(tab_aWhat[3] - tab_aWhere[3]) <= Tol_Mass && aPnt_aWhat.Distance(aPnt) <= Tol_1D )
        isFound = true;
      else {
        if ( (tab_aWhat[3] - tab_aWhere[3]) > Tol_Mass ) {
          aPntShape = BRepBuilderAPI_MakeVertex( aPnt ).Shape();
          aVertex   = TopoDS::Vertex( aPntShape );
          BRepExtrema_DistShapeShape aWhereDistance ( aVertex, Exp_aWhere.Current() );
          BRepExtrema_DistShapeShape aWhatDistance  ( aVertex, Exp_aWhat.Current() );
          if ( aWhereDistance.IsDone() && aWhatDistance.IsDone() &&
               fabs(aWhereDistance.Value() - aWhatDistance.Value()) <= Tol_1D )
          {
            // 0020162: "EDF 961 GEOM : Getinplace is getting additionnal orthogonal faces"
            // aVertex must be projected to the same point on Where and on What
            gp_Pnt pOnWhat  = aWhatDistance.PointOnShape2(1);
            gp_Pnt pOnWhere = aWhereDistance.PointOnShape2(1);
            isFound = ( pOnWhat.Distance(pOnWhere) <= Tol_1D );
            if ( isFound && iType == TopAbs_FACE )
            {
              // check normals at pOnWhat and pOnWhere
              const double angleTol = M_PI/180.;
              gp_Vec normToWhat  = GetNormal( TopoDS::Face(Exp_aWhat.Current()), aWhatDistance);
              gp_Vec normToWhere = GetNormal( TopoDS::Face(Exp_aWhere.Current()), aWhereDistance);
              if ( normToWhat * normToWhere < 0 )
                normToWhat.Reverse();
              isFound = ( normToWhat.Angle( normToWhere ) < angleTol );
            }
          }
        }
      }
      if ( isFound ) {
        theShapesInPlace.Append(Exp_aWhere.Current());
        //aWhere_Mass += tab_aWhere[3];
        isFound = false;
        break;
      }
    }
  }

  if (theShapesInPlace.Extent() == 0) {
    // Not found any Results
    return 3;
  }

  return 0;
}

//=======================================================================
//function : GetNormal
//purpose  : 
//=======================================================================
gp_Vec GEOMAlgo_GetInPlaceAPI::GetNormal
                         (const TopoDS_Face                &theFace,
                          const BRepExtrema_DistShapeShape &theExtrema)
{
  gp_Vec defaultNorm(1,0,0); // to have same normals on different faces
  try {
    // get UV at extrema point
    Standard_Real u,v, f,l;
    switch ( theExtrema.SupportTypeShape2(1) ) {
    case BRepExtrema_IsInFace: {
      theExtrema.ParOnFaceS2(1, u, v );
      break;
    }
    case BRepExtrema_IsOnEdge: {
      TopoDS_Edge edge = TopoDS::Edge( theExtrema.SupportOnShape2(1));
      Handle(Geom2d_Curve) pcurve =
        BRep_Tool::CurveOnSurface(edge, theFace, f,l);

      theExtrema.ParOnEdgeS2( 1, u );
      gp_Pnt2d uv = pcurve->Value( u );
      u = uv.Coord(1);
      v = uv.Coord(2);
      break;
    }
    case BRepExtrema_IsVertex: return defaultNorm;
    }
    // get derivatives
    BRepAdaptor_Surface surface( theFace, false );
    gp_Vec du, dv; gp_Pnt p;
    surface.D1( u, v, p, du, dv );

    return du ^ dv;

  } catch (Standard_Failure ) {
  }
  return defaultNorm;
}

//=======================================================================
//function : GetShapeProperties
//purpose  : 
//=======================================================================
void GEOMAlgo_GetInPlaceAPI::GetShapeProperties(const TopoDS_Shape  &theShape,
                                                      Standard_Real  theTab[],
                                                      gp_Pnt        &theVertex)
{
  GProp_GProps  aProps;
  gp_Pnt        aCenterMass;
  Standard_Real aShapeSize;

  if    (theShape.ShapeType() == TopAbs_VERTEX) {
    aCenterMass = BRep_Tool::Pnt(TopoDS::Vertex(theShape));
  } else if (theShape.ShapeType() == TopAbs_EDGE) {
    BRepGProp::LinearProperties(theShape,  aProps);
  } else if (theShape.ShapeType() == TopAbs_FACE) {
    BRepGProp::SurfaceProperties(theShape, aProps);
  } else {
    BRepGProp::VolumeProperties(theShape,  aProps);
  }

  if (theShape.ShapeType() == TopAbs_VERTEX) {
    aShapeSize = 1;
  } else {
    aCenterMass = aProps.CentreOfMass();
    aShapeSize  = aProps.Mass();
  }

  theVertex = aCenterMass;
  theTab[0] = theVertex.X();
  theTab[1] = theVertex.Y();
  theTab[2] = theVertex.Z();
  theTab[3] = aShapeSize;
}

//=======================================================================
//function : GetInPlaceByHistory
//purpose  : 
//=======================================================================
Standard_Boolean GEOMAlgo_GetInPlaceAPI::GetInPlaceByHistory
                      (const Handle(GEOM_Function)      &theWhereFunction,
                       const TopTools_IndexedMapOfShape &theWhereIndices,
                       const TopoDS_Shape               &theWhat,
                             TopTools_ListOfShape       &theShapesInPlace)
{
  if (theWhereFunction.IsNull() || theWhat.IsNull())
    return Standard_False;

  if (theWhereIndices.Contains(theWhat)) {
    // entity was not changed by the operation
    theShapesInPlace.Append(theWhat);

    return Standard_True;
  }

  // try to find in history
  TDF_Label aHistoryLabel = theWhereFunction->GetHistoryEntry(Standard_False);

  // search in history for all argument shapes
  Standard_Boolean isFound = Standard_False;
  Standard_Boolean isGood = Standard_False;

  TDF_LabelSequence aLabelSeq;
  theWhereFunction->GetDependency(aLabelSeq);
  Standard_Integer nbArg = aLabelSeq.Length();

  for (Standard_Integer iarg = 1; iarg <= nbArg && !isFound; iarg++) {

    TDF_Label anArgumentRefLabel = aLabelSeq.Value(iarg);

    Handle(GEOM_Object) anArgumentObject = GEOM_Object::GetReferencedObject(anArgumentRefLabel);
    TopoDS_Shape anArgumentShape = anArgumentObject->GetValue();

    TopTools_IndexedMapOfShape anArgumentIndices;
    TopExp::MapShapes(anArgumentShape, anArgumentIndices);

    if (anArgumentIndices.Contains(theWhat)) {
      isFound = Standard_True;
      Standard_Integer aWhatIndex = anArgumentIndices.FindIndex(theWhat);

      // Find corresponding label in history
      TDF_Label anArgumentHistoryLabel =
        theWhereFunction->GetArgumentHistoryEntry(anArgumentRefLabel, Standard_False);
      if (anArgumentHistoryLabel.IsNull()) {
        // Lost History of operation argument. Possibly, all its entities was removed.
        isGood = Standard_True;
      }
      else {
        TDF_Label aWhatHistoryLabel = anArgumentHistoryLabel.FindChild(aWhatIndex, Standard_False);

        if (aWhatHistoryLabel.IsNull()) {
          // Removed entity ? Compound ? Compsolid ? Shell ? Wire
          isGood = Standard_False;
        } else {
          Handle(TDataStd_IntegerArray) anIntegerArray;
          if (!aWhatHistoryLabel.FindAttribute(TDataStd_IntegerArray::GetID(), anIntegerArray)) {
            //Error: Empty modifications history for the sought shape.
            isGood = Standard_False;
          }
          else {
            isGood = Standard_True;
            Standard_Integer imod, aModifLen = anIntegerArray->Array()->Length();
            for (imod = 1; imod <= aModifLen; imod++) {
              const Standard_Integer anIndex =
                anIntegerArray->Array()->Value(imod);

              theShapesInPlace.Append(theWhereIndices.FindKey(anIndex));
            }
          }
        }
      }
    }
  }

  isFound = isGood;

  if (!isFound) {
    // try compound/compsolid/shell/wire element by element
    Standard_Boolean isFoundAny = Standard_False;
    TopTools_MapOfShape mapShape;

    if (theWhat.ShapeType() == TopAbs_COMPOUND ||
        theWhat.ShapeType() == TopAbs_COMPSOLID) {
      // recursive processing of compound/compsolid
      TopoDS_Iterator anIt (theWhat, Standard_True, Standard_True);
      for (; anIt.More(); anIt.Next()) {
        if (mapShape.Add(anIt.Value())) {
          TopoDS_Shape curWhat = anIt.Value();
          isFoundAny = GetInPlaceByHistory(theWhereFunction, theWhereIndices, curWhat, theShapesInPlace);
          if (isFoundAny) isFound = Standard_True;
        }
      }
    }
    else if (theWhat.ShapeType() == TopAbs_SHELL) {
      // try to replace a shell by its faces images
      TopExp_Explorer anExp (theWhat, TopAbs_FACE);
      for (; anExp.More(); anExp.Next()) {
        if (mapShape.Add(anExp.Current())) {
          TopoDS_Shape curWhat = anExp.Current();
          isFoundAny = GetInPlaceByHistory(theWhereFunction, theWhereIndices, curWhat, theShapesInPlace);
          if (isFoundAny) isFound = Standard_True;
        }
      }
    }
    else if (theWhat.ShapeType() == TopAbs_WIRE) {
      // try to replace a wire by its edges images
      TopExp_Explorer anExp (theWhat, TopAbs_EDGE);
      for (; anExp.More(); anExp.Next()) {
        if (mapShape.Add(anExp.Current())) {
          TopoDS_Shape curWhat = anExp.Current();
          isFoundAny = GetInPlaceByHistory(theWhereFunction, theWhereIndices, curWhat, theShapesInPlace);
          if (isFoundAny) isFound = Standard_True;
        }
      }
    }
    else {
      // Removed entity
    }
  }

  return isFound;
}
