// Created on: 1992-01-23
// Created by: Didier PIFFAULT
// Copyright (c) 1992-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 <TopAbs.hxx>
#include <TopTrans_CurveTransition.hxx>

#define GREATER 1
#define SAME 0
#define LOWER -1

//=======================================================================
// function : TopTrans_CurveTransition
// purpose  : Empty Constructor.
//=======================================================================

TopTrans_CurveTransition::TopTrans_CurveTransition()
    : myCurv(0.0),
      Init(Standard_False),
      CurvFirst(0.0),
      CurvLast(0.0)
{
}

//=======================================================================
// function : Reset
// purpose  : Initializer for a complex curve transition with the elements
//           of the intersecting curve.
//=======================================================================

void TopTrans_CurveTransition::Reset(const gp_Dir&       Tgt,
                                     const gp_Dir&       Norm,
                                     const Standard_Real Curv)
{
  myTgt  = Tgt;
  myNorm = Norm;
  myCurv = Curv;
  Init   = Standard_True;
}

//=======================================================================
// function : Reset
// purpose  : Initializer for a complex curve transition with the elements
//           of the intersecting straight line.
//=======================================================================

void TopTrans_CurveTransition::Reset(const gp_Dir& Tgt)
{
  myTgt  = Tgt;
  myCurv = 0.;
  Init   = Standard_True;
}

//=======================================================================
// function : Compare
// purpose  : Compare the elements of  an interference  on  an intersected
//           curve with the interference stored in the complex Transition.
//=======================================================================

void TopTrans_CurveTransition::Compare(const Standard_Real      Tole,
                                       const gp_Dir&            T,
                                       const gp_Dir&            N,
                                       const Standard_Real      C,
                                       const TopAbs_Orientation St,
                                       const TopAbs_Orientation Or)
{
  // S is the transition, how the curve cross the boundary
  // O is the orientation, how the intersection is set on the boundary
  TopAbs_Orientation S = St;
  TopAbs_Orientation O = Or;

  // adjustment for INTERNAL transition
  if (S == TopAbs_INTERNAL)
  {
    if (T * myTgt < 0)
      S = TopAbs::Reverse(O);
    else
      S = O;
  }

  // It is the first comparison for this complex transition
  if (Init)
  {
    Init      = Standard_False;
    TgtFirst  = T;
    NormFirst = N;
    CurvFirst = C;
    TranFirst = S;
    TgtLast   = T;
    NormLast  = N;
    CurvLast  = C;
    TranLast  = S;
    switch (O)
    {
        // Interference en fin d'arete il faut inverser la tangente
      case TopAbs_REVERSED:
        TgtFirst.Reverse();
        TgtLast.Reverse();
        break;
      case TopAbs_INTERNAL:
        // Interference en milieu d'arete il faut inverser en fonction de la
        // position de la tangente de reference
        if (myTgt * T > 0)
          TgtFirst.Reverse();
        else
          TgtLast.Reverse();
        break;
      case TopAbs_FORWARD:
      case TopAbs_EXTERNAL:
        break;
    }
  }

  // Compare with the existent first and last transition :
  else
  {
    Standard_Boolean FirstSet    = Standard_False;
    Standard_Real    cosAngWithT = myTgt * T;
    switch (O)
    {
      case TopAbs_REVERSED:
        cosAngWithT = -cosAngWithT;
        break;
      case TopAbs_INTERNAL:
        if (cosAngWithT > 0)
          cosAngWithT = -cosAngWithT;
        break;
      case TopAbs_FORWARD:
      case TopAbs_EXTERNAL:
        break;
    }
    Standard_Real cosAngWith1 = myTgt * TgtFirst;

    switch (Compare(cosAngWithT, cosAngWith1, Tole))
    {

      case LOWER:
        // If the angle is greater than the first the new become the first
        FirstSet = Standard_True;
        TgtFirst = T;
        switch (O)
        {
          case TopAbs_REVERSED:
            TgtFirst.Reverse();
            break;
          case TopAbs_INTERNAL:
            if (myTgt * T > 0)
              TgtFirst.Reverse();
            break;
          case TopAbs_FORWARD:
          case TopAbs_EXTERNAL:
            break;
        }
        NormFirst = N;
        CurvFirst = C;
        TranFirst = S;
        break;

      case SAME:
        // If same angles we look at the Curvature
        if (IsBefore(Tole, cosAngWithT, N, C, NormFirst, CurvFirst))
        {
          FirstSet = Standard_True;
          TgtFirst = T;
          switch (O)
          {
            case TopAbs_REVERSED:
              TgtFirst.Reverse();
              break;
            case TopAbs_INTERNAL:
              if (myTgt * T > 0)
                TgtFirst.Reverse();
              break;
            case TopAbs_FORWARD:
            case TopAbs_EXTERNAL:
              break;
          }
          NormFirst = N;
          CurvFirst = C;
          TranFirst = S;
        }
        break;

      case GREATER:
        break;
    }

    if (!FirstSet || O == TopAbs_INTERNAL)
    {
      // Dans les cas de tangence le premier peut etre aussi le dernier
      if (O == TopAbs_INTERNAL)
        cosAngWithT = -cosAngWithT;
      Standard_Real cosAngWith2 = myTgt * TgtLast;

      switch (Compare(cosAngWithT, cosAngWith2, Tole))
      {

        case GREATER:
          // If the angle is lower than the last the new become the last
          TgtLast = T;
          switch (O)
          {
            case TopAbs_REVERSED:
              TgtLast.Reverse();
              break;
            case TopAbs_INTERNAL:
              if (myTgt * T < 0)
                TgtLast.Reverse();
              break;
            case TopAbs_FORWARD:
            case TopAbs_EXTERNAL:
              break;
          }
          NormLast = N;
          CurvLast = C;
          TranLast = S;
          break;

        case SAME:
          // If the angle is the same we look at the curvature
          if (IsBefore(Tole, cosAngWithT, NormLast, CurvLast, N, C))
          {
            TgtLast = T;
            switch (O)
            {
              case TopAbs_REVERSED:
                TgtLast.Reverse();
                break;
              case TopAbs_INTERNAL:
                if (myTgt * T < 0)
                  TgtLast.Reverse();
                break;
              case TopAbs_FORWARD:
              case TopAbs_EXTERNAL:
                break;
            }
            NormLast = N;
            CurvLast = C;
            TranLast = S;
          }
      }
    }
  }
}

//=======================================================================
// function : StateBefore
// purpose  : Give the state of the curv before the interference.
//=======================================================================

TopAbs_State TopTrans_CurveTransition::StateBefore() const
{
  if (Init)
    return TopAbs_UNKNOWN;
  switch (TranFirst)
  {
    case TopAbs_FORWARD:
    case TopAbs_EXTERNAL:
      return TopAbs_OUT;
    case TopAbs_REVERSED:
    case TopAbs_INTERNAL:
      return TopAbs_IN;
  }
  return TopAbs_OUT;
}

//=======================================================================
// function : StateAfter
// purpose  : give the state of the curve after the interference.
//=======================================================================

TopAbs_State TopTrans_CurveTransition::StateAfter() const
{
  if (Init)
    return TopAbs_UNKNOWN;
  switch (TranLast)
  {
    case TopAbs_FORWARD:
    case TopAbs_INTERNAL:
      return TopAbs_IN;
    case TopAbs_REVERSED:
    case TopAbs_EXTERNAL:
      return TopAbs_OUT;
  }
  return TopAbs_OUT;
}

//=======================================================================
// function : IsBefore
// purpose  : Compare the curvature of the two transition and return true
//           if T1 is before T2
//=======================================================================

Standard_Boolean TopTrans_CurveTransition::IsBefore(const Standard_Real Tole,
                                                    const Standard_Real CosAngl,
                                                    const gp_Dir&       N1,
                                                    const Standard_Real C1,
                                                    const gp_Dir&       N2,
                                                    const Standard_Real C2) const
{
  Standard_Real    TN1       = myTgt * N1;
  Standard_Real    TN2       = myTgt * N2;
  Standard_Boolean OneBefore = Standard_False;

  if (Abs(TN1) <= Tole || Abs(TN2) <= Tole)
  {
    // Tangent : The first is the interference which have the nearest curvature
    //           from the reference.
    if (myCurv == 0)
    {
      // The reference is straight
      // The first is the interference which have the lowest curvature.
      if (C1 < C2)
        OneBefore = Standard_True;
      //  Modified by Sergey KHROMOV - Wed Dec 27 17:08:49 2000 Begin
      if (CosAngl > 0)
        OneBefore = !OneBefore;
      //  Modified by Sergey KHROMOV - Wed Dec 27 17:08:50 2000 End
    }
    else
    {
      // The reference is curv
      // The first is the interference which have the nearest curvature
      // in the direction
      Standard_Real deltaC1, deltaC2;
      if (C1 == 0. || myCurv == 0.)
      {
        deltaC1 = C1 - myCurv;
      }
      else
      {
        deltaC1 = (C1 - myCurv) * (N1 * myNorm);
      }
      if (C2 == 0. || myCurv == 0.)
      {
        deltaC2 = C2 - myCurv;
      }
      else
      {
        deltaC2 = (C2 - myCurv) * (N2 * myNorm);
      }
      if (deltaC1 < deltaC2)
        OneBefore = Standard_True;
      if (CosAngl > 0)
        OneBefore = !OneBefore;
    }
  }
  else if (TN1 < 0)
  {
    // Before the first interference we are in the curvature
    if (TN2 > 0)
    {
      // Before the second  interference we are out the curvature
      // The first interference is before  /* ->)( */
      OneBefore = Standard_True;
    }
    else
    {
      // Before the second interference we are in the curvature
      if (C1 > C2)
      {
        // We choice the greater curvature
        // The first interference is before   /* ->)) */
        OneBefore = Standard_True;
      }
    }
  }
  else if (TN1 > 0)
  {
    // Before the first interference we are out the curvature
    if (TN2 > 0)
    {
      // Before the second interference we are out the curvature /* ->(( */
      if (C1 < C2)
      {
        // We choice the lower curvature
        // The first interference is before
        OneBefore = Standard_True;
      }
    }
  }
  return OneBefore;
}

//=======================================================================
// function : Compare
// purpose  : Compare two angles
//=======================================================================

Standard_Integer TopTrans_CurveTransition::Compare(const Standard_Real Ang1,
                                                   const Standard_Real Ang2,
                                                   const Standard_Real Tole) const
{
  Standard_Integer res = SAME;
  if (Ang1 - Ang2 > Tole)
    res = GREATER;
  else if (Ang2 - Ang1 > Tole)
    res = LOWER;

  return res;
}
