// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

// 22.12.98 dce S3767
// 21.12.98 rln, gka S4054
// #74 rln,pdn 11.03.99 S4135: Setting minimum and maximum tolerances according to static parameters
// sln 13.06.2002 OCC448 : Correction in  method TransferGeometry to avoid transferring invisible
// sub entities

#include <BRep_Builder.hxx>
#include <BRepLib.hxx>
#include <Geom_Surface.hxx>
#include <GeomAdaptor_Surface.hxx>
#include <gp_GTrsf.hxx>
#include <gp_Trsf.hxx>
#include <gp_Vec.hxx>
#include <IGESBasic_GroupWithoutBackP.hxx>
#include <IGESBasic_SingularSubfigure.hxx>
#include <IGESBasic_SubfigureDef.hxx>
#include <IGESData_IGESEntity.hxx>
#include <IGESData_IGESModel.hxx>
#include <IGESData_ToolLocation.hxx>
#include <IGESToBRep.hxx>
#include <IGESToBRep_BRepEntity.hxx>
#include <IGESToBRep_CurveAndSurface.hxx>
#include <IGESToBRep_TopoCurve.hxx>
#include <IGESToBRep_TopoSurface.hxx>
#include <MoniTool_Macros.hxx>
#include <Interface_Static.hxx>
#include <Message_Messenger.hxx>
#include <Message_Msg.hxx>
#include <Message_ProgressScope.hxx>
#include <Precision.hxx>
#include <Standard_ErrorHandler.hxx>
#include <Standard_Failure.hxx>
#include <TopLoc_Location.hxx>
#include <TopoDS_Compound.hxx>
#include <TopoDS_Shape.hxx>
#include <Transfer_TransientProcess.hxx>
#include <TransferBRep_ShapeBinder.hxx>
#include <TransferBRep_ShapeListBinder.hxx>

#include <cstdio>

//=================================================================================================

IGESToBRep_CurveAndSurface::IGESToBRep_CurveAndSurface()
    : myEps(1.e-04),
      myEpsCoeff(1.e-06),
      myEpsGeom(1.e-04),
      myMinTol(-1.0),
      myMaxTol(-1.0),
      myModeIsTopo(true),
      myModeApprox(false),
      myContIsOpti(false),
      myUnitFactor(1.0),
      mySurfaceCurve(0),
      myContinuity(0),
      myUVResolution(0.0),
      myIsResolCom(false),
      myTP(new Transfer_TransientProcess())
{
  UpdateMinMaxTol();
}

//=================================================================================================

IGESToBRep_CurveAndSurface::IGESToBRep_CurveAndSurface(const double eps,
                                                       const double epsCoeff,
                                                       const double epsGeom,
                                                       const bool   mode,
                                                       const bool   modeapprox,
                                                       const bool   optimized)
    : myEps(eps),
      myEpsCoeff(epsCoeff),
      myEpsGeom(epsGeom),
      myMinTol(-1.0),
      myMaxTol(-1.0),
      myModeIsTopo(mode),
      myModeApprox(modeapprox),
      myContIsOpti(optimized),
      myUnitFactor(1.0),
      mySurfaceCurve(0),
      myContinuity(0),
      myUVResolution(0.0),
      myIsResolCom(false),
      myTP(new Transfer_TransientProcess())
{
  UpdateMinMaxTol();
}

//=================================================================================================

void IGESToBRep_CurveAndSurface::Init()
{
  myEps          = 1.E-04;
  myEpsCoeff     = 1.E-06;
  myEpsGeom      = 1.E-04;
  myModeIsTopo   = true;
  myModeApprox   = false;
  myContIsOpti   = false;
  myUnitFactor   = 1.;
  mySurfaceCurve = 0;
  myContinuity   = 0;
  myTP           = new Transfer_TransientProcess();

  mySurface.Nullify();
  myIsResolCom   = false;
  myUVResolution = 0.;
  UpdateMinMaxTol();
}

//=================================================================================================

void IGESToBRep_CurveAndSurface::SetEpsGeom(const double eps)
{
  myEpsGeom = eps;
  UpdateMinMaxTol();
}

//=================================================================================================

void IGESToBRep_CurveAndSurface::UpdateMinMaxTol()
{
  // #74 rln 11.03.99 S4135: Setting maximum tolerances according to
  // static parameter
  myMaxTol = std::max(Interface_Static::RVal("read.maxprecision.val"), myEpsGeom * myUnitFactor);
  myMinTol = Precision::Confusion();
}

//=================================================================================================

void IGESToBRep_CurveAndSurface::SetModel(const occ::handle<IGESData_IGESModel>& model)
{
  myModel           = model;
  double unitfactor = myModel->GlobalSection().UnitValue();
  if (unitfactor != 1.)
  {
    if (myTP->TraceLevel() > 2)
      myTP->Messenger()->SendInfo() << "UnitFactor = " << unitfactor << std::endl;
    myUnitFactor = unitfactor;
  }
  UpdateMinMaxTol();
}

//=================================================================================================

TopoDS_Shape IGESToBRep_CurveAndSurface::TransferCurveAndSurface(
  const occ::handle<IGESData_IGESEntity>& start,
  const Message_ProgressRange&            theProgress)
{
  TopoDS_Shape res;
  if (start.IsNull())
  {
    Message_Msg msg1005("IGES_1005");
    SendFail(start, msg1005);
    return res;
  }
  occ::handle<TCollection_HAsciiString> label = GetModel()->StringLabel(start);
  //  int typeNumber = start->TypeNumber();

  if (IGESToBRep::IsTopoCurve(start))
  {
    IGESToBRep_TopoCurve TC(*this);
    res = TC.TransferTopoCurve(start);
  }
  else if (IGESToBRep::IsTopoSurface(start))
  {
    IGESToBRep_TopoSurface TS(*this);
    res = TS.TransferTopoSurface(start);
  }
  else if (IGESToBRep::IsBRepEntity(start))
  {
    IGESToBRep_BRepEntity TS(*this);
    res = TS.TransferBRepEntity(start, theProgress);
  }
  else
  {
    Message_Msg msg1015("IGES_1015");
    SendFail(start, msg1015);
    // AddFail(start, "The IGESEntity is not a curve a Surface or a BRep Entity.");
  }
  // mjm le 12/09/96
  /*
    if (!res.IsNull()) {
      try {
        OCC_CATCH_SIGNALS
        double Eps = GetEpsGeom()*GetUnitFactor();
        BRepLib::SameParameter(res,Eps);
      }
      catch(Standard_Failure) {
      Message_Msg msg1010("IGES_1010");
        SendWarning (start,msg1010);
      }
    }
  */
  return res;
}

//=================================================================================================

TopoDS_Shape IGESToBRep_CurveAndSurface::TransferGeometry(
  const occ::handle<IGESData_IGESEntity>& start,
  const Message_ProgressRange&            theProgress)
{
  // Declaration of messages//
  // DCE 22/12/98
  // Message_Msg msg1005("IGES_1005");  //  Software error :  start IsNull.
  // Message_Msg msg1015("IGES_1015");  //  invalid type or exception raising (software error).
  // Message_Msg msg1010("IGES_1010");  //  Not sameparameter.
  //  Message_Msg msg1015("IGES_1015");
  // Message_Msg msg210 ("XSTEP_210");
  // Message_Msg msg202 ("XSTEP_202");
  ////////////////////////////
  TopoDS_Shape res;
  gp_Trsf      T408;
  if (start.IsNull())
  {
    Message_Msg msg1005("IGES_1005"); //  Software error :  start IsNull.
    SendFail(start, msg1005);
    return res;
  }

  // Read of the DE number and the type number of the entity
  occ::handle<TCollection_HAsciiString> label = GetModel()->StringLabel(start);
  // int typeNumber = start->TypeNumber();

  // sln 13.06.2002 OCC448: Avoid transferring invisible sub entities which
  // logically depend on the one
  int onlyvisible = Interface_Static::IVal("read.iges.onlyvisible");

  if (IGESToBRep::IsCurveAndSurface(start))
  {
    if (onlyvisible && start->BlankStatus() == 1)
      return res;
    try
    {
      OCC_CATCH_SIGNALS
      res = TransferCurveAndSurface(start, theProgress);
    }
    catch (Standard_Failure const&)
    {
      Message_Msg msg1015("IGES_1015");
      SendFail(start, msg1015);
    }
    return res;
  }

  // 408 : SingularSubfigure
  if (start->IsKind(STANDARD_TYPE(IGESBasic_SingularSubfigure)))
  {
    if (onlyvisible && start->BlankStatus() == 1)
      return res;

    DeclareAndCast(IGESBasic_SingularSubfigure, st408, start);
    occ::handle<IGESBasic_SubfigureDef> stsub = st408->Subfigure();
    gp_XYZ                              trans = st408->Translation();
    gp_Vec                              vectr(trans);
    double                              scunit = GetUnitFactor();
    vectr.Multiply(scunit);
    T408.SetTranslation(vectr);
    if (st408->HasScaleFactor())
    {
      double scalef = st408->ScaleFactor();
      T408.SetScaleFactor(scalef);
    }
    if (HasShapeResult(stsub))
    {
      res = GetShapeResult(stsub);
    }
    else
    {
      try
      {
        OCC_CATCH_SIGNALS
        res = TransferGeometry(stsub, theProgress);
      }
      catch (Standard_Failure const&)
      {
        res.Nullify();
        Message_Msg msg1015("IGES_1015");
        SendFail(st408, msg1015);
      }
      if (!res.IsNull())
      {
        SetShapeResult(stsub, res);
      }
    }
  }

  // 308 : SubfigureDefinition
  else if (start->IsKind(STANDARD_TYPE(IGESBasic_SubfigureDef)))
  {
    DeclareAndCast(IGESBasic_SubfigureDef, st308, start);
    TopoDS_Compound group;
    BRep_Builder    B;
    B.MakeCompound(group);
    if (st308->NbEntities() < 1)
    {
      Message_Msg msg210("XSTEP_210");
      SendFail(st308, msg210);
      return res;
    }
    Message_ProgressScope PS(theProgress, "Subfigure item", st308->NbEntities());
    for (int i = 1; i <= st308->NbEntities() && PS.More(); i++)
    {
      Message_ProgressRange aRange = PS.Next();
      TopoDS_Shape          item;
      if (st308->AssociatedEntity(i).IsNull())
      {
        Message_Msg msg1020("IGES_1020");
        msg1020.Arg(i);
        SendWarning(st308, msg1020);
        continue;
      }
      if (onlyvisible && st308->AssociatedEntity(i)->BlankStatus() == 1)
        continue;

      if (HasShapeResult(st308->AssociatedEntity(i)))
      {
        item = GetShapeResult(st308->AssociatedEntity(i));
      }
      else
      {
        try
        {
          OCC_CATCH_SIGNALS
          item = TransferGeometry(st308->AssociatedEntity(i), aRange);
        }
        catch (Standard_Failure const&)
        {
          item.Nullify();
          Message_Msg msg1015("IGES_1015");
          SendFail(st308->AssociatedEntity(i), msg1015);
        }
      }
      if (item.IsNull())
      {
        Message_Msg msg1025("IGES_1025");
        msg1025.Arg(i);
        SendWarning(start, msg1025);
      }
      else
      {
        B.Add(group, item);
        SetShapeResult(st308->AssociatedEntity(i), item);
      }
    }
    res = group;
  }
  else if (start->IsKind(STANDARD_TYPE(IGESBasic_Group)))
  {
    if (onlyvisible && start->BlankStatus() == 1)
      return res;

    DeclareAndCast(IGESBasic_Group, st402f1, start);
    TopoDS_Compound group;
    BRep_Builder    B;
    B.MakeCompound(group);
    if (st402f1->NbEntities() < 1)
    {
      Message_Msg msg202("XSTEP_202");
      msg202.Arg(st402f1->FormNumber());
      SendFail(st402f1, msg202);
      return res;
    }
    Message_ProgressScope PS(theProgress, "Group item", st402f1->NbEntities());
    bool                  ProblemInGroup = false;
    for (int i = 1; i <= st402f1->NbEntities() && PS.More(); i++)
    {
      Message_ProgressRange aRange = PS.Next();
      TopoDS_Shape          item;
      if (st402f1->Entity(i).IsNull())
      {
        Message_Msg msg1020("IGES_1020");
        msg1020.Arg(i);
        SendFail(st402f1, msg1020);
        continue;
      }

      if (onlyvisible && st402f1->Entity(i)->BlankStatus() == 1)
        continue;

      if (HasShapeResult(st402f1->Entity(i)))
      {
        item = GetShapeResult(st402f1->Entity(i));
      }
      else
      {
        try
        {
          OCC_CATCH_SIGNALS
          item = TransferGeometry(st402f1->Entity(i), aRange);
        }
        catch (Standard_Failure const&)
        {
          item.Nullify();
          Message_Msg msg1015("IGES_1015");
          SendFail(st402f1->Entity(i), msg1015);
        }
      }
      if (item.IsNull())
      {
        // Message_Msg msg1030("IGES_1030");
        // msg1030.Arg(st402f1->FormNumber());
        // msg1030.Arg(i);
        // SendWarning (st402f1,msg1030);
        ProblemInGroup = true;
      }
      else
      {
        B.Add(group, item);
        SetShapeResult(st402f1->Entity(i), item);
      }
    }
    res = group;
    if (ProblemInGroup)
    {
      Message_Msg msg1030("IGES_1030");
      msg1030.Arg(st402f1->FormNumber());
      SendWarning(st402f1, msg1030);
    }
  }
  else if (start->IsKind(STANDARD_TYPE(IGESBasic_GroupWithoutBackP)))
  {

    if (onlyvisible && start->BlankStatus() == 1)
      return res;

    DeclareAndCast(IGESBasic_GroupWithoutBackP, st402f7, start);
    TopoDS_Compound group;
    // unused    occ::handle<TCollection_HAsciiString> label = GetModel()->StringLabel(st402f7);
    BRep_Builder B;
    B.MakeCompound(group);
    if (st402f7->NbEntities() < 1)
    {
      Message_Msg msg202("XSTEP_202");
      msg202.Arg(st402f7->FormNumber());
      SendFail(st402f7, msg202);
      return res;
    }
    Message_ProgressScope PS(theProgress, "Group item", st402f7->NbEntities());
    bool                  ProblemInGroup = false;
    for (int i = 1; i <= st402f7->NbEntities() && PS.More(); i++)
    {
      Message_ProgressRange aRange = PS.Next();
      TopoDS_Shape          item;
      if (st402f7->Entity(i).IsNull())
      {
        Message_Msg msg1020("IGES_1020");
        msg1020.Arg(i);
        SendFail(st402f7, msg1020);
        continue;
      }

      if (onlyvisible && st402f7->Entity(i)->BlankStatus() == 1)
        continue;

      if (HasShapeResult(st402f7->Entity(i)))
      {
        item = GetShapeResult(st402f7->Entity(i));
      }
      else
      {
        try
        {
          OCC_CATCH_SIGNALS
          item = TransferGeometry(st402f7->Entity(i), aRange);
        }
        catch (Standard_Failure const&)
        {
          item.Nullify();
          Message_Msg msg1015("IGES_1015");
          SendFail(st402f7->Entity(i), msg1015);
        }
      }
      if (item.IsNull())
      {
        // Message_Msg msg1030("IGES_1030");
        // msg1030.Arg(st402f7->FormNumber());
        // msg1030.Arg(i);
        // SendWarning (st402f7,msg1030);
        ProblemInGroup = true;
      }
      else
      {
        B.Add(group, item);
        SetShapeResult(st402f7->Entity(i), item);
      }
    }
    res = group;
    if (ProblemInGroup)
    {
      Message_Msg msg1030("IGES_1030");
      msg1030.Arg(st402f7->FormNumber());
      SendWarning(st402f7, msg1030);
    }
  }
  else
  {
    Message_Msg msg1001("IGES_1001");
    msg1001.Arg(start->FormNumber());
    SendFail(start, msg1001);
    return res;
  }

  if (start->HasTransf())
  {
    gp_Trsf T;
    SetEpsilon(1.E-04);
    if (IGESData_ToolLocation::ConvertLocation(GetEpsilon(),
                                               start->CompoundLocation(),
                                               T,
                                               GetUnitFactor()))
    {
      if (start->IsKind(STANDARD_TYPE(IGESBasic_SingularSubfigure)))
      {
        gp_XYZ tra   = T.TranslationPart();
        gp_XYZ trans = T408.TranslationPart();
        tra.Add(trans);
        T.SetTranslationPart(tra);
        double sc     = T.ScaleFactor();
        double scalef = T408.ScaleFactor();
        sc            = sc * scalef;
        T.SetScaleFactor(sc);
      }
      TopLoc_Location L(T);
      res.Move(L, false);
    }
    else
    {
      Message_Msg msg1035("IGES_1035");
      SendWarning(start, msg1035);
    }
  }
  else
  {
    if (start->IsKind(STANDARD_TYPE(IGESBasic_SingularSubfigure)))
    {
      TopLoc_Location L(T408);
      res.Move(L);
    }
  }
  return res;
}

//=================================================================================================

bool IGESToBRep_CurveAndSurface::HasShapeResult(const occ::handle<IGESData_IGESEntity>& start) const
{
  DeclareAndCast(TransferBRep_ShapeBinder, binder, myTP->Find(start));
  if (binder.IsNull())
    return false;
  return binder->HasResult();
}

//=================================================================================================

TopoDS_Shape IGESToBRep_CurveAndSurface::GetShapeResult(
  const occ::handle<IGESData_IGESEntity>& start) const
{
  TopoDS_Shape res;

  DeclareAndCast(TransferBRep_ShapeBinder, binder, myTP->Find(start));
  if (binder.IsNull())
    return res;
  if (binder->HasResult())
    res = binder->Result();
  return res;
}

//=================================================================================================

void IGESToBRep_CurveAndSurface::SetShapeResult(const occ::handle<IGESData_IGESEntity>& start,
                                                const TopoDS_Shape&                     result)
{
  occ::handle<TransferBRep_ShapeBinder> binder = new TransferBRep_ShapeBinder;
  myTP->Bind(start, binder);
  binder->SetResult(result);
}

//=================================================================================================

int IGESToBRep_CurveAndSurface::NbShapeResult(const occ::handle<IGESData_IGESEntity>& start) const
{
  int nbres = 0;
  DeclareAndCast(TransferBRep_ShapeListBinder, binder, myTP->Find(start));
  if (binder.IsNull())
    return nbres;
  nbres = binder->NbShapes();
  return nbres;
}

//=================================================================================================

TopoDS_Shape IGESToBRep_CurveAndSurface::GetShapeResult(
  const occ::handle<IGESData_IGESEntity>& start,
  const int                               num) const
{
  TopoDS_Shape res;

  DeclareAndCast(TransferBRep_ShapeListBinder, binder, myTP->Find(start));
  if (binder.IsNull())
    return res;

  if (num <= binder->NbShapes())
    res = binder->Shape(num);
  return res;
}

//=================================================================================================

void IGESToBRep_CurveAndSurface::AddShapeResult(const occ::handle<IGESData_IGESEntity>& start,
                                                const TopoDS_Shape&                     result)
{
  DeclareAndCast(TransferBRep_ShapeListBinder, binder, myTP->Find(start));
  if (binder.IsNull())
  {
    binder = new TransferBRep_ShapeListBinder;
    myTP->Bind(start, binder);
  }
  binder->AddResult(result);
}

void IGESToBRep_CurveAndSurface::SetSurface(const occ::handle<Geom_Surface>& theSurface)
{
  if (mySurface != theSurface)
  {
    mySurface      = theSurface;
    myIsResolCom   = false;
    myUVResolution = 0.;
  }
}

occ::handle<Geom_Surface> IGESToBRep_CurveAndSurface::Surface() const
{
  return mySurface;
}

double IGESToBRep_CurveAndSurface::GetUVResolution()
{
  if (!myIsResolCom && !mySurface.IsNull())
  {
    myIsResolCom = true;
    GeomAdaptor_Surface aGAS(mySurface);
    myUVResolution = std::min(aGAS.UResolution(1.), aGAS.VResolution(1.));
  }
  return myUVResolution;
}
