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

#include <LProp_Status.hxx>
#include <LProp_NotDefined.hxx>
#include <Standard_OutOfRange.hxx>

static const Standard_Real MinStep   = 1.0e-7;



LProp_CLProps::LProp_CLProps (const Curve& C,
                              const Standard_Real U,
                              const Standard_Integer N, 
                              const Standard_Real Resolution) 
      : myCurve(C), myDerOrder(N), myCN(4), 
        myLinTol(Resolution), myTangentStatus (LProp_Undecided)
{
  Standard_OutOfRange_Raise_if (N < 0 || N > 3,
                          "LProp_CLProps::LProp_CLProps()");

  SetParameter(U);
}

LProp_CLProps::LProp_CLProps (const Curve& C, const Standard_Integer N, 
                                    const Standard_Real Resolution) 
        : myCurve(C), myU(RealLast()), myDerOrder(N), myCN(4), 
            myLinTol(Resolution), myTangentStatus (LProp_Undecided)
{
  Standard_OutOfRange_Raise_if (N < 0 || N > 3, 
                          "LProp_CLProps::LProp_CLProps()");  
}

LProp_CLProps::LProp_CLProps (const Standard_Integer N, 
                              const Standard_Real Resolution) 
        : myU(RealLast()), myDerOrder(N), myCN(0), myLinTol(Resolution),
                            myTangentStatus (LProp_Undecided)
{
  Standard_OutOfRange_Raise_if (N < 0 || N > 3, "LProp_CLProps() - invalid input");
}

void LProp_CLProps::SetParameter(const Standard_Real U)
{
  myU = U;
  switch (myDerOrder)
  {
  case 0:
    Tool::Value(myCurve, myU, myPnt);
    break;
  case 1:
    Tool::D1(myCurve, myU, myPnt, myDerivArr[0]);
    break;
  case 2:
    Tool::D2(myCurve, myU, myPnt, myDerivArr[0], myDerivArr[1]);
    break;
  case 3:
    Tool::D3(myCurve, myU, myPnt, myDerivArr[0], myDerivArr[1], myDerivArr[2]);
    break;
  }

  myTangentStatus = LProp_Undecided;
}

void LProp_CLProps::SetCurve(const Curve& C)
{
  myCurve = C ; 
  myCN = 4; // Tool::Continuity(C); RLE
}

const Pnt& LProp_CLProps::Value () const
{
  return myPnt;
}

const Vec& LProp_CLProps::D1 ()
{
  if (myDerOrder < 1)
  {
    myDerOrder = 1;
    Tool::D1(myCurve, myU, myPnt, myDerivArr[0]);
  }

  return myDerivArr[0];
}

const Vec& LProp_CLProps::D2 ()
{
  if (myDerOrder < 2)
  {
    myDerOrder = 2;
    Tool::D2(myCurve, myU, myPnt, myDerivArr[0], myDerivArr[1]);
  }

  return myDerivArr[1];
}

const Vec& LProp_CLProps::D3 ()
{
  if (myDerOrder < 3)
  {
    myDerOrder = 3;
    Tool::D3(myCurve, myU, myPnt, myDerivArr[0], myDerivArr[1], myDerivArr[2]);
  }

  return myDerivArr[2];
}

Standard_Boolean LProp_CLProps::IsTangentDefined ()
{
  if (myTangentStatus == LProp_Undefined)
    return Standard_False;
  else if (myTangentStatus >= LProp_Defined)
    return Standard_True;

  // tangentStatus == Lprop_Undecided 
  // we have to calculate the first non null derivative
  const Standard_Real Tol = myLinTol * myLinTol;
  
  Vec V;
  
  Standard_Integer Order = 0;
  while (Order++ < 4)
  {
    if(myCN >= Order)
    {
      switch(Order)
      {
      case 1:
        V = D1();
        break;
      case 2:
        V = D2();
        break;
      case 3:
        V = D3();
        break;
      }//switch(Order)

      if(V.SquareMagnitude() > Tol)
      {
        mySignificantFirstDerivativeOrder = Order;
        myTangentStatus = LProp_Defined;
        return Standard_True;
      }//if(V.SquareMagnitude() > Tol)
    }//if(cn >= Order)
    else
    {
      myTangentStatus = LProp_Undefined;
      return Standard_False;
    }// else of "if(cn >= Order)" condition
  }//while (Order < 4)

  return Standard_False;
}

void  LProp_CLProps::Tangent (Dir& D)
{
  if(!IsTangentDefined())
    throw LProp_NotDefined();
  
  if(mySignificantFirstDerivativeOrder == 1)
    D = Dir(myDerivArr[0]);
  else if (mySignificantFirstDerivativeOrder > 1)
  {
    const Standard_Real DivisionFactor = 1.e-3;
    const Standard_Real anUsupremum = Tool::LastParameter(myCurve), 
                        anUinfium = Tool::FirstParameter(myCurve);
                        
    Standard_Real du;
    if((anUsupremum >= RealLast()) || (anUinfium <= RealFirst()))
      du = 0.0;
    else
      du = anUsupremum-anUinfium;
    
    const Standard_Real aDelta = Max(du*DivisionFactor,MinStep);

    Vec V = myDerivArr[mySignificantFirstDerivativeOrder - 1];
    
    Standard_Real u;
          
    if(myU-anUinfium < aDelta)
      u = myU+aDelta;
    else
      u = myU-aDelta;
    
    Pnt P1, P2;
    Tool::Value(myCurve, Min(myU, u),P1);
    Tool::Value(myCurve, Max(myU, u),P2);
    
    Vec V1(P1,P2);
    Standard_Real aDirFactor = V.Dot(V1);
    
    if(aDirFactor < 0.0)
      V = -V;
      
    D = Dir(V);
  }//else if (mySignificantFirstDerivativeOrder > 1)
}

Standard_Real LProp_CLProps::Curvature ()
{
  Standard_Boolean isDefined = IsTangentDefined();
  (void)isDefined; // trick to avoid compiler warning on variable unised in Release mode; note that IsTangentDefined() must be called always
  LProp_NotDefined_Raise_if(!isDefined,
          "LProp_CLProps::CurvatureNotDefined()");

  // if the first derivative is null the curvature is infinite.
  if(mySignificantFirstDerivativeOrder > 1)
    return RealLast();

  Standard_Real Tol = myLinTol * myLinTol;
  Standard_Real DD1 = myDerivArr[0].SquareMagnitude();
  Standard_Real DD2 = myDerivArr[1].SquareMagnitude();
  
  // if the second derivative is null the curvature is null.
  if (DD2 <= Tol)
  {
    myCurvature = 0.0;
  }
  else
  {
    Standard_Real N = myDerivArr[0].CrossSquareMagnitude(myDerivArr[1]);
    // if d[0] and d[1] are colinear the curvature is null.
    //Standard_Real t = N/(DD1*DD2);
    Standard_Real t = N / DD1 / DD2;
    if (t<=Tol)
    {
      myCurvature = 0.0;
    }
    else
    {
      myCurvature = sqrt(N) / DD1 / sqrt(DD1);
    }
  }

  return myCurvature;
}

void  LProp_CLProps::Normal (Dir& D)
{
  Standard_Real c = Curvature();
  if(c==RealLast() || Abs(c) <= myLinTol)
  {
    throw LProp_NotDefined("LProp_CLProps::Normal(...):"
        "Curvature is null or infinity"); 
  }

  // we used here the following vector relation 
  // a ^ (b ^ c) = b(ac) - c(ab)
  // Norm = d[0] ^ (d[1] ^ d[0])
  
  Vec Norm = myDerivArr[1] * (myDerivArr[0] * myDerivArr[0]) - myDerivArr[0] * (myDerivArr[0] * myDerivArr[1]);
  D = Dir(Norm);
}

void  LProp_CLProps::CentreOfCurvature (Pnt& P)
{
  if(Abs(Curvature()) <= myLinTol)
  {
    throw LProp_NotDefined();
  }

  // we used here the following vector relation 
  // a ^ (b ^ c) = b(ac) - c(ab)
  // Norm = d[0] ^ (d[1] ^ d[0])

  Vec Norm = myDerivArr[1] * (myDerivArr[0] * myDerivArr[0]) - myDerivArr[0] * (myDerivArr[0] * myDerivArr[1]);
  Norm.Normalize();
  Norm.Divide(myCurvature);
  P= myPnt.Translated(Norm);
}
