// 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.

#include <Adaptor3d_Curve.hxx>
#include <Bnd_Box.hxx>
#include <BndLib_Add3dCurve.hxx>
#include <GCPnts_QuasiUniformDeflection.hxx>
#include <gp_Circ.hxx>
#include <gp_Pnt.hxx>
#include <gp_Vec.hxx>
#include <Precision.hxx>
#include <Vrml_Coordinate3.hxx>
#include <Vrml_IndexedLineSet.hxx>
#include <Vrml_Material.hxx>
#include <Vrml_Separator.hxx>
#include <VrmlConverter_DeflectionCurve.hxx>
#include <VrmlConverter_Drawer.hxx>
#include <VrmlConverter_LineAspect.hxx>

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

static void FindLimits(const Adaptor3d_Curve& aCurve,
                       const Standard_Real    aLimit,
                       Standard_Real&         First,
                       Standard_Real&         Last)
{
  First                     = aCurve.FirstParameter();
  Last                      = aCurve.LastParameter();
  Standard_Boolean firstInf = Precision::IsNegativeInfinite(First);
  Standard_Boolean lastInf  = Precision::IsPositiveInfinite(Last);

  if (firstInf || lastInf)
  {
    gp_Pnt        P1, P2;
    Standard_Real delta = 1;
    if (firstInf && lastInf)
    {
      do
      {
        delta *= 2;
        First = -delta;
        Last  = delta;
        aCurve.D0(First, P1);
        aCurve.D0(Last, P2);
      } while (P1.Distance(P2) < aLimit);
    }
    else if (firstInf)
    {
      aCurve.D0(Last, P2);
      do
      {
        delta *= 2;
        First = Last - delta;
        aCurve.D0(First, P1);
      } while (P1.Distance(P2) < aLimit);
    }
    else if (lastInf)
    {
      aCurve.D0(First, P1);
      do
      {
        delta *= 2;
        Last = First + delta;
        aCurve.D0(Last, P2);
      } while (P1.Distance(P2) < aLimit);
    }
  }
}

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

static void PrintPoints(Handle(TColgp_HArray1OfVec)&        aHAV1,
                        Handle(TColStd_HArray1OfInteger)&   aHAI1,
                        const Handle(VrmlConverter_Drawer)& aDrawer,
                        Standard_OStream&                   anOStream)
{
  // creation of Vrml objects
  Handle(VrmlConverter_LineAspect) LA = new VrmlConverter_LineAspect;
  LA                                  = aDrawer->LineAspect();

  // Separator 1 {
  Vrml_Separator SE1;
  SE1.Print(anOStream);
  // Material
  if (LA->HasMaterial())
  {

    Handle(Vrml_Material) M;
    M = LA->Material();

    M->Print(anOStream);
  }
  // Coordinate3
  Handle(Vrml_Coordinate3) C3 = new Vrml_Coordinate3(aHAV1);
  C3->Print(anOStream);
  // IndexedLineSet
  Vrml_IndexedLineSet ILS;
  ILS.SetCoordIndex(aHAI1);
  ILS.Print(anOStream);
  // Separator 1 }
  SE1.Print(anOStream);
}

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

static void DrawCurve(Adaptor3d_Curve&                    aCurve,
                      const Standard_Real                 TheDeflection,
                      const Standard_Real                 U1,
                      const Standard_Real                 U2,
                      const Handle(VrmlConverter_Drawer)& aDrawer, // for passing of LineAspect
                      Standard_OStream&                   anOStream)
{
  Standard_Integer                 i;
  Standard_Boolean                 key = Standard_False;
  Handle(TColgp_HArray1OfVec)      HAV1;
  Handle(TColStd_HArray1OfInteger) HAI1;

  switch (aCurve.GetType())
  {
    case GeomAbs_Line: {
      gp_Vec V;
      key  = Standard_True;
      HAV1 = new TColgp_HArray1OfVec(1, 2);
      HAI1 = new TColStd_HArray1OfInteger(1, 3);

      // array of coordinates of line
      gp_Pnt p = aCurve.Value(U1);
      V.SetX(p.X());
      V.SetY(p.Y());
      V.SetZ(p.Z());
      HAV1->SetValue(1, V);

      p = aCurve.Value(U2);
      V.SetX(p.X());
      V.SetY(p.Y());
      V.SetZ(p.Z());
      HAV1->SetValue(2, V);

      // array of indexes of line
      HAI1->SetValue(1, 0);
      HAI1->SetValue(2, 1);
      HAI1->SetValue(3, -1);
    }
    break;
    case GeomAbs_Circle: {
      Standard_Real Radius = aCurve.Circle().Radius();
      if (!Precision::IsInfinite(Radius))
      {
        Standard_Real    DU = Sqrt(8.0 * TheDeflection / Radius);
        Standard_Integer N  = Standard_Integer(Abs(U2 - U1) / DU);

        if (N > 0)
        {

          gp_Vec V;
          key  = Standard_True;
          HAV1 = new TColgp_HArray1OfVec(1, N + 1);
          HAI1 = new TColStd_HArray1OfInteger(1, N + 2);

          DU = (U2 - U1) / N;
          Standard_Real U;
          gp_Pnt        p;

          for (Standard_Integer Index = 1; Index <= N + 1; Index++)
          {
            U = U1 + (Index - 1) * DU;
            p = aCurve.Value(U);

            V.SetX(p.X());
            V.SetY(p.Y());
            V.SetZ(p.Z());
            HAV1->SetValue(Index, V);
            HAI1->SetValue(Index, Index - 1);
          }
          /*
               if( HAV1->Value(1).IsEqual( HAV1->Value(N+1),Precision::Confusion(),
             Precision::Angular() ) )
                 {
                   HAI1->SetValue(N+1, 0);
                 }
          */
          HAI1->SetValue(HAI1->Upper(), -1);
        }
      }
    }
    break;

    default: {
      GCPnts_QuasiUniformDeflection Algo(aCurve, TheDeflection, U1, U2);
      if (Algo.IsDone())
      {

        Standard_Integer NumberOfPoints = Algo.NbPoints();
        if (NumberOfPoints > 0)
        {

          gp_Vec V;
          key  = Standard_True;
          HAV1 = new TColgp_HArray1OfVec(1, NumberOfPoints);
          HAI1 = new TColStd_HArray1OfInteger(1, NumberOfPoints + 1);
          gp_Pnt p;

          for (i = 1; i <= NumberOfPoints; i++)
          {
            p = Algo.Value(i);
            V.SetX(p.X());
            V.SetY(p.Y());
            V.SetZ(p.Z());
            HAV1->SetValue(i, V);
          }

          for (i = HAI1->Lower(); i < HAI1->Upper(); i++)
          {
            HAI1->SetValue(i, i - 1);
          }
          HAI1->SetValue(HAI1->Upper(), -1);
        }
      }
      // else
      // cannot draw with respect to a maximal chordial deviation
    }
  }

  // std::cout  << " Array HAI1 - coordIndex " << std::endl;
  //
  // for ( i=HAI1->Lower(); i <= HAI1->Upper(); i++ )
  //   {
  //     std::cout << HAI1->Value(i) << std::endl;
  //   }

  if (key)
  {
    PrintPoints(HAV1, HAI1, aDrawer, anOStream);
  }
}

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

static Standard_Real GetDeflection(const Adaptor3d_Curve&              aCurve,
                                   const Standard_Real                 U1,
                                   const Standard_Real                 U2,
                                   const Handle(VrmlConverter_Drawer)& aDrawer)
{

  Standard_Real theRequestedDeflection;
  if (aDrawer->TypeOfDeflection() == Aspect_TOD_RELATIVE) // TOD_RELATIVE, TOD_ABSOLUTE
  {
    Bnd_Box box;
    BndLib_Add3dCurve::Add(aCurve, U1, U2, Precision::Confusion(), box);

    Standard_Real Xmin, Xmax, Ymin, Ymax, Zmin, Zmax, diagonal;
    box.Get(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax);
    if (!(box.IsOpenXmin() || box.IsOpenXmax() || box.IsOpenYmin() || box.IsOpenYmax()
          || box.IsOpenZmin() || box.IsOpenZmax()))
    {
      diagonal               = Sqrt((Xmax - Xmin) * (Xmax - Xmin) + (Ymax - Ymin) * (Ymax - Ymin)
                      + (Zmax - Zmin) * (Zmax - Zmin));
      diagonal               = Max(diagonal, Precision::Confusion());
      theRequestedDeflection = aDrawer->DeviationCoefficient() * diagonal;
    }
    else
    {
      diagonal               = 1000000.;
      theRequestedDeflection = aDrawer->DeviationCoefficient() * diagonal;
    }
    //      std::cout << "diagonal = " << diagonal << std::endl;
    //      std::cout << "theRequestedDeflection = " << theRequestedDeflection << std::endl;
  }
  else
  {
    theRequestedDeflection = aDrawer->MaximalChordialDeviation();
  }
  return theRequestedDeflection;
}

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

void VrmlConverter_DeflectionCurve::Add(Standard_OStream&                   anOStream,
                                        Adaptor3d_Curve&                    aCurve,
                                        const Handle(VrmlConverter_Drawer)& aDrawer)
{

  Standard_Real V1, V2;
  Standard_Real aLimit = aDrawer->MaximalParameterValue();
  FindLimits(aCurve, aLimit, V1, V2);

  Standard_Real theRequestedDeflection = GetDeflection(aCurve, V1, V2, aDrawer);

  DrawCurve(aCurve, theRequestedDeflection, V1, V2, aDrawer, anOStream);
}

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

void VrmlConverter_DeflectionCurve::Add(Standard_OStream&                   anOStream,
                                        Adaptor3d_Curve&                    aCurve,
                                        const Standard_Real                 U1,
                                        const Standard_Real                 U2,
                                        const Handle(VrmlConverter_Drawer)& aDrawer)
{
  Standard_Real V1 = U1;
  Standard_Real V2 = U2;

  if (Precision::IsNegativeInfinite(V1))
    V1 = -aDrawer->MaximalParameterValue();
  if (Precision::IsPositiveInfinite(V2))
    V2 = aDrawer->MaximalParameterValue();

  Standard_Real theRequestedDeflection = GetDeflection(aCurve, V1, V2, aDrawer);
  DrawCurve(aCurve, theRequestedDeflection, V1, V2, aDrawer, anOStream);
}

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

void VrmlConverter_DeflectionCurve::Add(Standard_OStream&   anOStream,
                                        Adaptor3d_Curve&    aCurve,
                                        const Standard_Real aDeflection,
                                        const Standard_Real aLimit)
{
  Standard_Real V1, V2;
  FindLimits(aCurve, aLimit, V1, V2);

  Handle(VrmlConverter_Drawer)     aDrawer = new VrmlConverter_Drawer;
  Handle(VrmlConverter_LineAspect) la      = new VrmlConverter_LineAspect;
  aDrawer->SetLineAspect(la);

  DrawCurve(aCurve, aDeflection, V1, V2, aDrawer, anOStream);
}

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

void VrmlConverter_DeflectionCurve::Add(Standard_OStream&                   anOStream,
                                        Adaptor3d_Curve&                    aCurve,
                                        const Standard_Real                 aDeflection,
                                        const Handle(VrmlConverter_Drawer)& aDrawer)
{
  Standard_Real aLimit = aDrawer->MaximalParameterValue();
  Standard_Real V1, V2;
  FindLimits(aCurve, aLimit, V1, V2);

  DrawCurve(aCurve, aDeflection, V1, V2, aDrawer, anOStream);
}

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

void VrmlConverter_DeflectionCurve::Add(Standard_OStream&   anOStream,
                                        Adaptor3d_Curve&    aCurve,
                                        const Standard_Real U1,
                                        const Standard_Real U2,
                                        const Standard_Real aDeflection)
{
  Handle(VrmlConverter_Drawer)     aDrawer = new VrmlConverter_Drawer;
  Handle(VrmlConverter_LineAspect) la      = new VrmlConverter_LineAspect;
  aDrawer->SetLineAspect(la);

  DrawCurve(aCurve, aDeflection, U1, U2, aDrawer, anOStream);
}

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

void VrmlConverter_DeflectionCurve::Add(Standard_OStream&                    anOStream,
                                        const Adaptor3d_Curve&               aCurve,
                                        const Handle(TColStd_HArray1OfReal)& aParams,
                                        const Standard_Integer               aNbNodes,
                                        const Handle(VrmlConverter_Drawer)&  aDrawer)
{
  Handle(TColgp_HArray1OfVec)      aHAV1 = new TColgp_HArray1OfVec(1, aNbNodes);
  Handle(TColStd_HArray1OfInteger) aHAI1 = new TColStd_HArray1OfInteger(1, aNbNodes + 1);

  Standard_Integer i;
  gp_Pnt           aPoint;
  gp_Vec           aVec;
  for (i = 1; i <= aNbNodes; i++)
  {
    Standard_Real aParam = aParams->Value(aParams->Lower() + i - 1);
    aPoint               = aCurve.Value(aParam);
    aVec.SetX(aPoint.X());
    aVec.SetY(aPoint.Y());
    aVec.SetZ(aPoint.Z());
    aHAV1->SetValue(i, aVec);
  }

  for (i = aHAI1->Lower(); i < aHAI1->Upper(); i++)
  {
    aHAI1->SetValue(i, i - 1);
  }
  aHAI1->SetValue(aHAI1->Upper(), -1);

  PrintPoints(aHAV1, aHAI1, aDrawer, anOStream);
}
