// Created on: 2009-06-17
// Created by: Sergey ZARITCHNY
// Copyright (c) 2009-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.

#include <BRep_Tool.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepCheck_Analyzer.hxx>
#include <BRepCheck_Shell.hxx>
#include <BRepCheck_Wire.hxx>
#include <BRepGProp.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRepPrimAPI_MakeRevol.hxx>
#include <DNaming.hxx>
#include <DNaming_RevolutionDriver.hxx>
#include <Geom_Line.hxx>
#include <GProp_GProps.hxx>
#include <ModelDefinitions.hxx>
#include <Precision.hxx>
#include <Standard_Real.hxx>
#include <Standard_Type.hxx>
#include <TDataStd_Integer.hxx>
#include <TDataStd_Real.hxx>
#include <TDF_Label.hxx>
#include <TDF_TagSource.hxx>
#include <TFunction_Function.hxx>
#include <TFunction_Logbook.hxx>
#include <TNaming.hxx>
#include <TNaming_Builder.hxx>
#include <TNaming_NamedShape.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopLoc_Location.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Shape.hxx>
#include <TopTools_DataMapOfShapeShape.hxx>
#include <TopTools_MapOfShape.hxx>

IMPLEMENT_STANDARD_RTTIEXT(DNaming_RevolutionDriver, TFunction_Driver)

// OCAF
//=================================================================================================

DNaming_RevolutionDriver::DNaming_RevolutionDriver() {}

//=======================================================================
// function : Validate
// purpose  : Validates labels of a function in <theLog>.
//=======================================================================
void DNaming_RevolutionDriver::Validate(Handle(TFunction_Logbook)&) const {}

//=======================================================================
// function : MustExecute
// purpose  : Analyses in <theLog> if the loaded function must be executed
//=======================================================================
Standard_Boolean DNaming_RevolutionDriver::MustExecute(const Handle(TFunction_Logbook)&) const
{
  return Standard_True;
}

//=======================================================================
// function : Execute
// purpose  : Executes the function
//=======================================================================
Standard_Integer DNaming_RevolutionDriver::Execute(Handle(TFunction_Logbook)& theLog) const
{
  Handle(TFunction_Function) aFunction;
  Label().FindAttribute(TFunction_Function::GetID(), aFunction);
  if (aFunction.IsNull())
    return -1;

  // Save location
  Handle(TNaming_NamedShape) aPrevRevol = DNaming::GetFunctionResult(aFunction);
  TopLoc_Location            aLocation;
  if (!aPrevRevol.IsNull() && !aPrevRevol->IsEmpty())
  {
    aLocation = aPrevRevol->Get().Location();
  }

  // Basis for Revol
  Handle(TDataStd_UAttribute) aBasObject = DNaming::GetObjectArg(aFunction, REVOL_BASIS);
  Handle(TNaming_NamedShape)  aBasisNS   = DNaming::GetObjectValue(aBasObject);
  if (aBasisNS.IsNull() || aBasisNS->IsEmpty())
  {
    aFunction->SetFailure(WRONG_ARGUMENT);
    return -1;
  }

  const TopoDS_Shape& aBasis = aBasisNS->Get();
  TopoDS_Shape        aBASIS;
  if (aBasis.ShapeType() == TopAbs_WIRE)
  {
    Handle(BRepCheck_Wire) aCheck = new BRepCheck_Wire(TopoDS::Wire(aBasis));
    if (aCheck->Closed(Standard_True) == BRepCheck_NoError)
    {
      BRepBuilderAPI_MakeFace aMaker(TopoDS::Wire(aBasis), Standard_True); // Makes planar face
      if (aMaker.IsDone())
        aBASIS = aMaker.Face(); // aMaker.Face();
    }
  }
  else if (aBasis.ShapeType() == TopAbs_FACE)
    aBASIS = aBasis;
  if (aBASIS.IsNull())
  {
    aFunction->SetFailure(WRONG_ARGUMENT);
    return -1;
  }

  Handle(TNaming_NamedShape) aContextOfBasis;
  Standard_Boolean           anIsAttachment = Standard_False;
  if (DNaming::IsAttachment(aBasObject))
  {
    aContextOfBasis = DNaming::GetAttachmentsContext(aBasObject); // a Context of Revolution basis
    if (aContextOfBasis.IsNull() || aContextOfBasis->IsEmpty())
    {
      aFunction->SetFailure(WRONG_ARGUMENT);
      return -1;
    }
    anIsAttachment = Standard_True;
  }

  // Axis
  Handle(TDataStd_UAttribute) anAxObject = DNaming::GetObjectArg(aFunction, REVOL_AXIS);
  Handle(TNaming_NamedShape)  anAxNS     = DNaming::GetObjectValue(anAxObject);
  gp_Ax1                      anAXIS;
  TopoDS_Shape                aTopoDSAxis;
  if (anAxNS.IsNull() || anAxNS->IsEmpty())
  {
    aFunction->SetFailure(WRONG_ARGUMENT);
    return -1;
  }
  else
  {
    aTopoDSAxis               = anAxNS->Get();
    Standard_Boolean anAxisOK = Standard_False;
    if (!aTopoDSAxis.IsNull())
    {
      if (aTopoDSAxis.ShapeType() == TopAbs_EDGE || aTopoDSAxis.ShapeType() == TopAbs_WIRE)
      {
        if (aTopoDSAxis.ShapeType() == TopAbs_WIRE)
        {
          TopExp_Explorer anExplorer(aTopoDSAxis, TopAbs_EDGE);
          aTopoDSAxis = anExplorer.Current();
        }
        const TopoDS_Edge& anEdge = TopoDS::Edge(aTopoDSAxis);
        Standard_Real      aFirst, aLast;
        Handle(Geom_Curve) aCurve = BRep_Tool::Curve(anEdge, aFirst, aLast);
        if (aCurve->IsKind(STANDARD_TYPE(Geom_Line)))
        {
          Handle(Geom_Line) aLine = Handle(Geom_Line)::DownCast(aCurve);
          if (!aLine.IsNull())
          {
            anAXIS   = aLine->Position();
            anAxisOK = Standard_True;
          }
        }
      }
    }
    if (!anAxisOK)
    {
#ifdef OCCT_DEBUG
      std::cout << "RevolutionDriver:: Axis is not correct" << std::endl;
#endif
      aFunction->SetFailure(WRONG_ARGUMENT);
      return -1;
    }
  }

  if (aFunction->GetDriverGUID() == FULREVOL_GUID)
  {
    BRepPrimAPI_MakeRevol aMakeRevol(aBASIS, anAXIS, Standard_True);
    aMakeRevol.Build();
    if (!aMakeRevol.IsDone())
    {
      aFunction->SetFailure(ALGO_FAILED);
      return -1;
    }
    const TopoDS_Shape aResult = aMakeRevol.Shape();
    BRepCheck_Analyzer aCheckAnalyzer(aResult);
    if (!aCheckAnalyzer.IsValid(aResult))
    {
      aFunction->SetFailure(RESULT_NOT_VALID);
      return -1;
    }
    Standard_Boolean aVol = Standard_False;
    if (aResult.ShapeType() == TopAbs_SOLID)
      aVol = Standard_True;
    else if (aResult.ShapeType() == TopAbs_SHELL)
    {
      Handle(BRepCheck_Shell) aCheck = new BRepCheck_Shell(TopoDS::Shell(aResult));
      if (aCheck->Closed() == BRepCheck_NoError)
        aVol = Standard_True;
    }
    if (aVol)
    {
      GProp_GProps aGProp;
      BRepGProp::VolumeProperties(aResult, aGProp);
      if (aGProp.Mass() <= Precision::Confusion())
      {
        aFunction->SetFailure(RESULT_NOT_VALID);
        return -1;
      }
    }
    // Naming
    if (anIsAttachment)
      LoadNamingDS(RESPOSITION(aFunction), aMakeRevol, aBASIS, aContextOfBasis->Get());
    else
      LoadNamingDS(RESPOSITION(aFunction), aMakeRevol, aBASIS, aBASIS);
  }
  else if (aFunction->GetDriverGUID() == SECREVOL_GUID)
  {
    Standard_Real anANGLE = DNaming::GetReal(aFunction, REVOL_ANGLE)->Get();
    if (anANGLE <= Precision::Confusion())
    {
      aFunction->SetFailure(WRONG_ARGUMENT);
      return -1;
    }
    // Reverse
    Standard_Integer aRev = DNaming::GetInteger(aFunction, REVOL_REV)->Get();
    if (aRev)
      anAXIS.Reverse();

    BRepPrimAPI_MakeRevol aMakeRevol(aBASIS, anAXIS, anANGLE, Standard_True);
    aMakeRevol.Build();
    if (!aMakeRevol.IsDone())
    {
      aFunction->SetFailure(ALGO_FAILED);
      return -1;
    }
    const TopoDS_Shape aResult = aMakeRevol.Shape();
    BRepCheck_Analyzer aCheckAnalyzer(aResult);
    if (!aCheckAnalyzer.IsValid(aResult))
    {
      aFunction->SetFailure(RESULT_NOT_VALID);
      return -1;
    }
    Standard_Boolean aVol = Standard_False;
    if (aResult.ShapeType() == TopAbs_SOLID)
      aVol = Standard_True;
    else if (aResult.ShapeType() == TopAbs_SHELL)
    {
      Handle(BRepCheck_Shell) aCheck = new BRepCheck_Shell(TopoDS::Shell(aResult));
      if (aCheck->Closed() == BRepCheck_NoError)
        aVol = Standard_True;
    }
    if (aVol)
    {
      GProp_GProps aGProp;
      BRepGProp::VolumeProperties(aResult, aGProp);
      if (aGProp.Mass() <= Precision::Confusion())
      {
        aFunction->SetFailure(RESULT_NOT_VALID);
        return -1;
      }
    }

    // Naming
    if (anIsAttachment)
      LoadNamingDS(RESPOSITION(aFunction), aMakeRevol, aBASIS, aContextOfBasis->Get());
    else
      LoadNamingDS(RESPOSITION(aFunction), aMakeRevol, aBASIS, aBASIS);
  }
  else
  {
    aFunction->SetFailure(UNSUPPORTED_FUNCTION);
    return -1;
  }

  // restore location
  if (!aLocation.IsIdentity())
    TNaming::Displace(RESPOSITION(aFunction), aLocation, Standard_True);

  theLog->SetValid(RESPOSITION(aFunction), Standard_True);
  aFunction->SetFailure(DONE);
  return 0;
}

//=======================================================================
static void LoadSeamEdge(BRepPrimAPI_MakeRevol& mkRevol,
                         const TDF_Label&       ResultLabel,
                         const TopoDS_Shape&    ShapeIn)

{
  TopTools_MapOfShape View;
  TopExp_Explorer     ShapeExplorer(ShapeIn, TopAbs_EDGE);
  Standard_Boolean    isFound(Standard_False);
  for (; ShapeExplorer.More(); ShapeExplorer.Next())
  {
    const TopoDS_Shape& Root = ShapeExplorer.Current();
    if (!View.Add(Root))
      continue;
    const TopTools_ListOfShape&        Shapes = mkRevol.Generated(Root);
    TopTools_ListIteratorOfListOfShape ShapesIterator(Shapes);
    for (; ShapesIterator.More(); ShapesIterator.Next())
    {
      TopoDS_Shape newShape = ShapesIterator.Value();
      if (newShape.ShapeType() != TopAbs_FACE)
        continue;
      if (!Root.IsSame(newShape))
      {
        TopExp_Explorer exp(newShape, TopAbs_EDGE);
        for (; exp.More(); exp.Next())
        {
          if (BRep_Tool::IsClosed(TopoDS::Edge(exp.Current()), TopoDS::Face(newShape)))
          {
            TNaming_Builder Builder(ResultLabel.NewChild());
            Builder.Generated(exp.Current());
            isFound = Standard_True;
            break;
          }
        }
        if (isFound)
        {
          isFound = Standard_False;
          break;
        }
      }
    }
  }
}

//=======================================================================
static Standard_Boolean HasDangle(const TopoDS_Shape& ShapeIn)
{
  if (ShapeIn.ShapeType() == TopAbs_SOLID)
    return Standard_False;
  else if (ShapeIn.ShapeType() == TopAbs_SHELL)
  {
    Handle(BRepCheck_Shell) aCheck = new BRepCheck_Shell(TopoDS::Shell(ShapeIn));
    return aCheck->Closed() != BRepCheck_NoError;
  }
  else if (ShapeIn.ShapeType() == TopAbs_FACE || ShapeIn.ShapeType() == TopAbs_WIRE
           || ShapeIn.ShapeType() == TopAbs_EDGE || ShapeIn.ShapeType() == TopAbs_VERTEX)
    return Standard_True;
  return Standard_False;
}

//=======================================================================
static void BuildAtomicMap(const TopoDS_Shape& S, TopTools_MapOfShape& M)
{
  if (S.ShapeType() == TopAbs_COMPOUND || S.ShapeType() == TopAbs_COMPSOLID)
  {
    TopoDS_Iterator it(S);
    for (; it.More(); it.Next())
    {
      if (it.Value().ShapeType() > TopAbs_COMPSOLID)
        M.Add(it.Value());
      else
        BuildAtomicMap(it.Value(), M);
    }
  }
  else
    M.Add(S);
}

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

Standard_Boolean HasDangleShapes(const TopoDS_Shape& ShapeIn)
{
  if (ShapeIn.ShapeType() == TopAbs_COMPOUND || ShapeIn.ShapeType() == TopAbs_COMPSOLID)
  {
    TopTools_MapOfShape M;
    BuildAtomicMap(ShapeIn, M);
    TopTools_MapIteratorOfMapOfShape it(M);
    for (; it.More(); it.Next())
      if (HasDangle(it.Key()))
        return Standard_True;
  }
  else
    return HasDangle(ShapeIn);
  return Standard_False;
}

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

void DNaming_RevolutionDriver::LoadNamingDS(const TDF_Label&       theResultLabel,
                                            BRepPrimAPI_MakeRevol& MS,
                                            const TopoDS_Shape&    Basis,
                                            const TopoDS_Shape&    Context) const
{

  TopTools_DataMapOfShapeShape SubShapes;
  for (TopExp_Explorer Exp(MS.Shape(), TopAbs_FACE); Exp.More(); Exp.Next())
  {
    SubShapes.Bind(Exp.Current(), Exp.Current());
  }

  Handle(TDF_TagSource) Tagger = TDF_TagSource::Set(theResultLabel);
  if (Tagger.IsNull())
    return;
  Tagger->Set(0);

  TNaming_Builder Builder(theResultLabel);
  if (Basis.IsEqual(Context))
    Builder.Generated(MS.Shape());
  else
    Builder.Generated(Context, MS.Shape());

  // Insert lateral face : Face from Edge
  TNaming_Builder LateralFaceBuilder(theResultLabel.NewChild());
  DNaming::LoadAndOrientGeneratedShapes(MS, Basis, TopAbs_EDGE, LateralFaceBuilder, SubShapes);

  // is full
  TopoDS_Shape     StartShape = MS.FirstShape();
  TopoDS_Shape     EndShape   = MS.LastShape();
  Standard_Boolean isFull(Standard_False);
  if (!StartShape.IsNull() && !EndShape.IsNull())
    isFull = StartShape.IsEqual(EndShape);

  Standard_Boolean hasDangle = HasDangleShapes(MS.Shape());
  Standard_Boolean isBasisClosed(Standard_True);
  TopoDS_Vertex    Vfirst, Vlast;
  if (Basis.ShapeType() == TopAbs_WIRE)
  {
    Handle(BRepCheck_Wire) aCheck = new BRepCheck_Wire(TopoDS::Wire(Basis));
    if (aCheck->Closed() != BRepCheck_NoError)
    {
      isBasisClosed = Standard_False; // open
      TopExp::Vertices(TopoDS::Wire(Basis), Vfirst, Vlast);
    }
  }
  else if (Basis.ShapeType() == TopAbs_EDGE)
  {
    BRepBuilderAPI_MakeWire aMakeWire;
    aMakeWire.Add(TopoDS::Edge(Basis));
    if (aMakeWire.IsDone())
    {
      Handle(BRepCheck_Wire) aCheck = new BRepCheck_Wire(aMakeWire.Wire());
      if (aCheck->Closed() != BRepCheck_NoError)
      {                                 // check for circle case
        isBasisClosed = Standard_False; // open
        TopExp::Vertices(TopoDS::Edge(Basis), Vfirst, Vlast);
      }
    }
  }
  if (isFull)
  {
    // seam edge
    LoadSeamEdge(MS, theResultLabel, Basis);

    if (hasDangle)
    {
      if (!isBasisClosed)
      {
        // dangle edges
        const TopTools_ListOfShape&        Shapes = MS.Generated(Vfirst);
        TopTools_ListIteratorOfListOfShape it(Shapes);
        for (; it.More(); it.Next())
        {
          if (!BRep_Tool::Degenerated(TopoDS::Edge(it.Value())))
          {
            TNaming_Builder aBuilder(theResultLabel.NewChild());
            aBuilder.Generated(Vfirst, it.Value());
          }
#ifdef OCCT_DEBUG
          else
          {
            if (MS.HasDegenerated())
              std::cout << "mkRevol has degenerated" << std::endl;
            std::cout << "BRep_Tool found degenerated edge (from Vfirst) TS = "
                      << it.Value().TShape().get() << std::endl;
          }
#endif
        }

        const TopTools_ListOfShape& Shapes2 = MS.Generated(Vlast);
        it.Initialize(Shapes2);
        for (; it.More(); it.Next())
        {
          if (!BRep_Tool::Degenerated(TopoDS::Edge(it.Value())))
          {
            TNaming_Builder aBuilder(theResultLabel.NewChild());
            aBuilder.Generated(Vlast, it.Value());
          }
#ifdef OCCT_DEBUG
          else
          {
            if (MS.HasDegenerated())
              std::cout << "mkRevol has degenerated" << std::endl;
            std::cout << "BRep_Tool found degenerated edge (from Vlast) TS = "
                      << it.Value().TShape().get() << std::endl;
          }
#endif
        }
      }
    }
  }
  else
  { // if(!isFull)
    // Insert start shape
    if (!StartShape.IsNull())
    {
      if (StartShape.ShapeType() != TopAbs_COMPOUND)
      {
        TNaming_Builder StartBuilder(theResultLabel.NewChild());
        if (SubShapes.IsBound(StartShape))
        {
          StartShape = SubShapes(StartShape);
        }
        StartBuilder.Generated(StartShape);
        if (StartShape.ShapeType() != TopAbs_FACE)
        {
          TopoDS_Iterator it(StartShape);
          for (; it.More(); it.Next())
          {
            TNaming_Builder aBuilder(theResultLabel.NewChild());
            aBuilder.Generated(it.Value());
          }
        }
      }
      else
      {
        TopoDS_Iterator itr(StartShape);
        for (; itr.More(); itr.Next())
        {
          TNaming_Builder StartBuilder(theResultLabel.NewChild());
          StartBuilder.Generated(itr.Value());
        }
      }
    }

    // Insert end shape
    if (!EndShape.IsNull())
    {
      if (EndShape.ShapeType() != TopAbs_COMPOUND)
      {
        TNaming_Builder EndBuilder(theResultLabel.NewChild());
        if (SubShapes.IsBound(EndShape))
        {
          EndShape = SubShapes(EndShape);
        }
        EndBuilder.Generated(EndShape);
        if (EndShape.ShapeType() != TopAbs_FACE)
        {
          TopoDS_Iterator it(EndShape);
          for (; it.More(); it.Next())
          {
            TNaming_Builder aBuilder(theResultLabel.NewChild());
            aBuilder.Generated(it.Value());
          }
        }
      }
      else
      {
        TopoDS_Iterator itr(EndShape);
        for (; itr.More(); itr.Next())
        {
          TNaming_Builder EndBuilder(theResultLabel.NewChild());
          EndBuilder.Generated(itr.Value());
        }
      }
    }
    if (hasDangle)
    {
      if (!isBasisClosed)
      {
        // dangle edges
        const TopTools_ListOfShape&        Shapes = MS.Generated(Vfirst);
        TopTools_ListIteratorOfListOfShape it(Shapes);
        for (; it.More(); it.Next())
        {
          if (!BRep_Tool::Degenerated(TopoDS::Edge(it.Value())))
          {
            TNaming_Builder aBuilder(theResultLabel.NewChild());
            aBuilder.Generated(Vfirst, it.Value());
          }
#ifdef OCCT_DEBUG
          else
          {
            if (MS.HasDegenerated())
              std::cout << "mkRevol has degenerated" << std::endl;
            std::cout << "BRep_Tool found degenerated edge (from Vfirst) TS = "
                      << it.Value().TShape().get() << std::endl;
          }
#endif
        }

        const TopTools_ListOfShape& Shapes2 = MS.Generated(Vlast);
        it.Initialize(Shapes2);
        for (; it.More(); it.Next())
        {
          if (!BRep_Tool::Degenerated(TopoDS::Edge(it.Value())))
          {
            TNaming_Builder aBuilder(theResultLabel.NewChild());
            aBuilder.Generated(Vlast, it.Value());
          }
#ifdef OCCT_DEBUG
          else
          {
            if (MS.HasDegenerated())
              std::cout << "mkRevol has degenerated" << std::endl;
            std::cout << "BRep_Tool found degenerated edge (from Vlast) TS = "
                      << it.Value().TShape().get() << std::endl;
          }
#endif
        }
      }
    }
  }
}
