// Copyright (c) 1995-1999 Matra Datavision
// 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.

#ifndef OCCT_DEBUG
#define No_Standard_RangeError
#define No_Standard_OutOfRange
#endif


void IntWalk_IWalking::AddPointInCurrentLine
         (const Standard_Integer N,
	  const ThePointOfPath& PathPnt,
	  const Handle(IntWalk_TheIWLine)& CurrentLine) const {


  IntSurf_PntOn2S Psol;
  Psol.SetValue(ThePointOfPathTool::Value3d(PathPnt),
		reversed,wd1[N].ustart,wd1[N].vstart);
  CurrentLine->AddPoint(Psol);
}


void IntWalk_IWalking::MakeWalkingPoint
         (const Standard_Integer Case, 
	  const Standard_Real U, 
	  const Standard_Real V,
	  TheIWFunction& sp,
	  IntSurf_PntOn2S& Psol )

{

// Case == 1      : make a WalkinkPoint.
// Case == 2      : make a WalkinkPoint.
//                  The computation of the tangency on is done  
// Case == 10 + i : make a WalkinkPoint according to i.
//                  but F is updated according to U and V
// Case == other  : the exception Standard_Failure is raised.

  if ((Case == 1) || (Case == 2))
  {
    Psol.SetValue(sp.Point(), reversed, U, V);
  }
  else if (Case == 11 || Case == 12)
  {
    Standard_Real aUV[2] = {}, aFF[1] = {}, aDD[1][2] = {};
    math_Vector UV(aUV, 1, 2);
    math_Vector FF(aFF, 1, 1);
    math_Matrix DD(aDD, 1, 1, 1, 2);
    UV(1) = U;
    UV(2) = V;
    sp.Values(UV, FF, DD);
    MakeWalkingPoint(Case - 10, U, V, sp, Psol);
  }
  else
  {
    throw Standard_ConstructionError();
  }
}



void IntWalk_IWalking::OpenLine(const Standard_Integer N,
                                const IntSurf_PntOn2S& Psol,
				const ThePOPIterator& Pnts1,
				TheIWFunction& sp,
                                const Handle(IntWalk_TheIWLine)& Line )
// **************** open the line and restart in the other direction********

{
  ThePointOfPath PathPnt;

  Standard_Real aUV[2], aFF[1], aDD[1][2];
  math_Vector UV(aUV,1, 2);
  math_Vector FF(aFF,1, 1);
  math_Matrix DD(aDD,1, 1, 1, 2); 

  previousPoint = Line->Value(1);  
  if (!reversed) {
    previousPoint.ParametersOnS2(UV(1),UV(2));
  }
  else {
    previousPoint.ParametersOnS1(UV(1),UV(2));
  }
  sp.Values(UV, FF, DD);
  previousd3d = sp.Direction3d();
  previousd2d = sp.Direction2d();

  if (N>0) { //departure point given at input
    PathPnt = Pnts1.Value(N);
    //mark the line as open with a given stop point
    Line->AddStatusFirst(Standard_False,Standard_True,N,PathPnt); 


    AddPointInCurrentLine(N,PathPnt,Line);

  }
  else  {
    if (N <0) Line->AddPoint(Psol);                      
    Line->AddStatusFirst(Standard_False,Standard_False);
       //mark the line as open without given stop point 
  }
  Line->Reverse();  //inverser la ligne        
  Line->SetTangentVector(previousd3d.Reversed(),Line->NbPoints());
}

Standard_Boolean IntWalk_IWalking::IsValidEndPoint(const Standard_Integer IndOfPoint,
                                                   const Standard_Integer IndOfLine)
{
  if (PointLineLine.IsEmpty())
    return Standard_True;
  
  TColStd_ListIteratorOfListOfInteger itl(PointLineLine(IndOfPoint));
  for (; itl.More(); itl.Next())
    if (itl.Value() == IndOfLine)
    {
      PointLineLine(IndOfPoint).Remove(itl);
      return Standard_True;
    }
  return Standard_False;
}

void IntWalk_IWalking::RemoveTwoEndPoints(const Standard_Integer IndOfPoint)
{
  if (PointLineLine.IsBound(IndOfPoint))
  {
    Standard_Integer Line1 = PointLineLine(IndOfPoint).First();
    Standard_Integer Line2 = PointLineLine(IndOfPoint).Last();
    for (Standard_Integer iseq = 1; iseq <= seqAlone.Length(); iseq++)
    {
      if (seqAlone(iseq) == Line1 ||
          seqAlone(iseq) == Line2)
        seqAlone.Remove(iseq--);
    }
  }
}

Standard_Boolean IntWalk_IWalking::IsPointOnLine(const gp_Pnt2d& theP2d,
                                                 const Standard_Integer Irang)
{
  const Handle(IntWalk_TheIWLine)& aLine = lines.Value(Abs(Irang));
  for (Standard_Integer i = 1; i <= aLine->NbPoints(); i++)
  {
    gp_Pnt2d P2d1 = aLine->Value(i).ValueOnSurface(reversed);
    if (Abs(P2d1.X() - theP2d.X()) <= tolerance(1) &&
        Abs(P2d1.Y() - theP2d.Y()) <= tolerance(2))
      return Standard_True;
    if (i < aLine->NbPoints())
    {
      gp_Pnt2d P2d2 = aLine->Value(i+1).ValueOnSurface(reversed);
      gp_Vec2d PP1(theP2d, P2d1);
      gp_Vec2d PP2(theP2d, P2d2);
      if (PP1 * PP2 < 0)
        return Standard_True;
    }
  }
  return Standard_False;
}

//==================================================================================
//function : IsPointOnLine
//purpose  : Projects thePOn2S on the nearest segment of the already computed line.
//           The retrieved projection point (aPa) is refined using theSolver.
//            After the refinement, we will obtain a point aPb.
//            If thePOn2S is quite far from aPb then thePOn2S is not
//            in the line.
//           Every already computed line is checked.
//==================================================================================
Standard_Boolean IntWalk_IWalking::IsPointOnLine(const IntSurf_PntOn2S& thePOn2S,
                                                 const math_Vector& theInfBounds,
                                                 const math_Vector& theSupBounds,
                                                 math_FunctionSetRoot& theSolver,
                                                 TheIWFunction& theFunc)
{
  const Standard_Real eps = Epsilon(1.);
  const gp_Pnt &aP3d = thePOn2S.Value();

  for (Standard_Integer aLIdx = 1; aLIdx <= lines.Length(); aLIdx++)
  {
    const Handle(IntSurf_LineOn2S) &aL = lines(aLIdx)->Line();

    if (aL->IsOutBox(aP3d))
      continue;

    //Look for the nearest segment
    Standard_Real aUMin = 0.0, aVMin = 0.0;
    Standard_Real aMinSqDist = RealLast();
    for (Standard_Integer aPtIdx = 1; aPtIdx < aL->NbPoints(); aPtIdx++)
    {
      const gp_Pnt &aP1 = aL->Value(aPtIdx).Value();
      const gp_Pnt &aP2 = aL->Value(aPtIdx + 1).Value();

      const gp_XYZ aP1P(aP3d.XYZ() - aP1.XYZ());
      const gp_XYZ aP1P2(aP2.XYZ() - aP1.XYZ());

      const Standard_Real aSq12 = aP1P2.SquareModulus();

      if (aSq12 < gp::Resolution())
        continue;

      const Standard_Real aDP = aP1P.Dot(aP1P2);

      Standard_Real aSqD = RealLast();
      if (aDP < 0.0)
      {
        continue;
      }
      else if (aDP > aSq12)
      {
        continue;
      }
      else
      {
        aSqD = aP1P.CrossSquareMagnitude(aP1P2) / aSq12;
      }

      if (aSqD < aMinSqDist)
      {
        aMinSqDist = aSqD;

        const Standard_Real aL1 = aDP / aSq12;
        const Standard_Real aL2 = 1.0 - aL1;

        if (aL1 < eps || aL2 < eps)
        {
          return Standard_True;
        }

        Standard_Real aU1, aV1, aU2, aV2;
        aL->Value(aPtIdx).ParametersOnSurface(reversed, aU1, aV1);
        aL->Value(aPtIdx + 1).ParametersOnSurface(reversed, aU2, aV2);

        aUMin = aL1*aU2 + aL2*aU1;
        aVMin = aL1*aV2 + aL2*aV1;
      }
    }

    if (aMinSqDist > Precision::Infinite())
      continue;

    math_Vector aVecPrms(1, 2);
    aVecPrms(1) = aUMin;
    aVecPrms(2) = aVMin;
    theSolver.Perform(theFunc, aVecPrms, theInfBounds, theSupBounds);
    if (!theSolver.IsDone())
      continue;

    theSolver.Root(aVecPrms);

    const gp_Pnt aPa(theFunc.PSurface()->Value(aUMin, aVMin)),
                 aPb(theFunc.PSurface()->Value(aVecPrms(1), aVecPrms(2)));
    const Standard_Real aSqD1 = aPb.SquareDistance(aP3d);
    const Standard_Real aSqD2 = aPa.SquareDistance(aPb);
 
    if (aSqD1 < 4.0*aSqD2)
    {
      return Standard_True;
    }
  }

  return Standard_False;
}
