// Created on: 1991-09-24
// Created by: Remi GILET
// Copyright (c) 1991-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 <ElCLib.hxx>
#include <GccAna_Circ2d2TanRad.hxx>
#include <GccEnt_BadQualifier.hxx>
#include <GccEnt_QualifiedCirc.hxx>
#include <gp_Circ2d.hxx>
#include <gp_Dir2d.hxx>
#include <gp_Lin2d.hxx>
#include <gp_Pnt2d.hxx>
#include <IntAna2d_AnaIntersection.hxx>
#include <IntAna2d_IntPoint.hxx>
#include <Standard_NegativeValue.hxx>

// circulare tangent to a circle a point and a given radius
//=============================================================
//========================================================================
// Initialize WellDone to false.                                       +
// Return circle C1.                                             +
// Leave with error if the construction is impossible.     +
// Create parallel to C1 in the proper direction.                           +
// Create circle with center in Point1 of radius Radius.                   +
// Intersect the parallel and the circle.                              +
//                              ==> The center point of the solution.   +
// Create the solution that will be added to already found solutions.         +
// Fill the fields.                                                +
//========================================================================
GccAna_Circ2d2TanRad::GccAna_Circ2d2TanRad(const GccEnt_QualifiedCirc& Qualified1,
                                           const gp_Pnt2d&             Point2,
                                           const double                Radius,
                                           const double                Tolerance)
    : qualifier1(1, 4),
      qualifier2(1, 4),
      TheSame1(1, 4),
      TheSame2(1, 4),
      cirsol(1, 4),
      pnttg1sol(1, 4),
      pnttg2sol(1, 4),
      par1sol(1, 4),
      par2sol(1, 4),
      pararg1(1, 4),
      pararg2(1, 4)
{

  gp_Dir2d dirx(gp_Dir2d::D::X);
  double   Tol = std::abs(Tolerance);
  NbrSol       = 0;
  WellDone     = false;
  if (!(Qualified1.IsEnclosed() || Qualified1.IsEnclosing() || Qualified1.IsOutside()
        || Qualified1.IsUnqualified()))
  {
    throw GccEnt_BadQualifier();
    return;
  }
  int i;
  for (i = 1; i <= 4; i++)
  {
    TheSame1(i) = 0;
    TheSame2(i) = 0;
  }
  double                        deport = 0.;
  int                           signe  = 0;
  int                           nbsol  = 0;
  gp_Circ2d                     C1     = Qualified1.Qualified();
  NCollection_Array1<gp_Circ2d> C(1, 4);
  double                        R1       = C1.Radius();
  double                        distance = (C1.Location()).Distance(Point2);
  double                        dispc1   = C1.Distance(Point2);
  gp_Dir2d                      dir1(Point2.XY() - (C1.Location().XY()));
  gp_Pnt2d                      center1(C1.Location());
  if (Radius < 0.0)
  {
    throw Standard_NegativeValue();
  }
  else
  {
    if (dispc1 - Radius * 2.0 > Tol)
    {
      WellDone = true;
    }
    else if (Qualified1.IsEnclosed())
    {
      //  =================================
      if ((distance - R1 > Tol) || (Radius - R1 > Tol))
      {
        WellDone = true;
      }
      else
      {
        if (std::abs(distance - R1) < Tol)
        {
          nbsol  = -1;
          deport = R1 - Radius;
          signe  = 1;
        }
        else
        {
          C(1)  = gp_Circ2d(C1.XAxis(), std::abs(Radius - R1));
          C(2)  = gp_Circ2d(gp_Ax2d(Point2, dirx), Radius);
          nbsol = 1;
        }
      }
    }
    else if (Qualified1.IsEnclosing())
    {
      //  ==================================
      if ((Tol < R1 - distance) || (Tol < R1 - Radius))
      {
        WellDone = true;
      }
      else
      {
        if (std::abs(distance - R1) < Tol)
        {
          nbsol  = -1;
          deport = R1 - Radius;
          signe  = 1;
        }
        else
        {
          C(1)  = gp_Circ2d(C1.XAxis(), std::abs(Radius - R1));
          C(2)  = gp_Circ2d(gp_Ax2d(Point2, dirx), Radius);
          nbsol = 1;
        }
      }
    }
    else if (Qualified1.IsOutside())
    {
      //  ================================
      if (Tol < R1 - distance)
      {
        WellDone = true;
      }
      else if ((std::abs(distance - R1) < Tol) || (std::abs(dispc1 - Radius * 2.0) < Tol))
      {
        nbsol  = -1;
        deport = R1 + Radius;
        signe  = -1;
      }
      else
      {
        C(1)  = gp_Circ2d(C1.XAxis(), Radius + R1);
        C(2)  = gp_Circ2d(gp_Ax2d(Point2, dirx), Radius);
        nbsol = 1;
      }
    }
    else if (Qualified1.IsUnqualified())
    {
      //  ====================================
      if (std::abs(dispc1 - Radius * 2.0) < Tol)
      {
        WellDone = true;
        gp_Pnt2d Center(center1.XY() + (distance - Radius) * dir1.XY());
        cirsol(1) = gp_Circ2d(gp_Ax2d(Center, dirx), Radius);
        //      ==================================================
        if (std::abs(Center.Distance(center1) - R1) < Tol)
        {
          qualifier1(1) = GccEnt_enclosed;
        }
        else
        {
          qualifier1(1) = GccEnt_outside;
        }
        qualifier2(1) = GccEnt_noqualifier;
        pnttg1sol(1)  = gp_Pnt2d(Center.XY() - Radius * dir1.XY());
        pnttg2sol(1)  = Point2;
        WellDone      = true;
        NbrSol        = 1;
      }
      else if ((std::abs(R1 - Radius) < Tol) && (std::abs(distance - R1) < Tol))
      {
        cirsol(1) = gp_Circ2d(C1);
        //      =========================
        qualifier1(1) = GccEnt_unqualified;
        qualifier2(1) = GccEnt_noqualifier;
        TheSame1(1)   = 1;
        pnttg2sol(1)  = Point2;
        WellDone      = true;
        NbrSol        = 1;
        C(1)          = gp_Circ2d(C1.XAxis(), Radius + R1);
        C(2)          = gp_Circ2d(gp_Ax2d(Point2, dirx), Radius);
        nbsol         = 1;
      }
      else
      {
        C(1)  = gp_Circ2d(C1.XAxis(), std::abs(Radius - R1));
        C(2)  = gp_Circ2d(gp_Ax2d(Point2, dirx), Radius);
        C(3)  = gp_Circ2d(C1.XAxis(), Radius + R1);
        C(4)  = gp_Circ2d(gp_Ax2d(Point2, dirx), Radius);
        nbsol = 2;
      }
    }
    if (nbsol > 0)
    {
      for (int j = 1; j <= nbsol; j++)
      {
        IntAna2d_AnaIntersection Intp(C(2 * j - 1), C(2 * j));
        if (Intp.IsDone())
        {
          if (!Intp.IsEmpty())
          {
            for (i = 1; i <= Intp.NbPoints(); i++)
            {
              NbrSol++;
              gp_Pnt2d Center(Intp.Point(i).Value());
              cirsol(NbrSol) = gp_Circ2d(gp_Ax2d(Center, dirx), Radius);
              //            =======================================================
              double distcc1 = center1.Distance(Center);
              if (!Qualified1.IsUnqualified())
              {
                qualifier1(NbrSol) = Qualified1.Qualifier();
              }
              else if (std::abs(distcc1 + Radius - R1) < Tol)
              {
                qualifier1(NbrSol) = GccEnt_enclosed;
              }
              else if (std::abs(distcc1 - R1 - Radius) < Tol)
              {
                qualifier1(NbrSol) = GccEnt_outside;
              }
              else
              {
                qualifier1(NbrSol) = GccEnt_enclosing;
              }
              qualifier2(NbrSol) = GccEnt_noqualifier;
              dir1               = gp_Dir2d(Center.XY() - center1.XY());
#ifdef OCCT_DEBUG
              gp_Dir2d dir2(Center.XY() - Point2.XY());
#endif
              if ((Center.Distance(center1) > C1.Radius())
                  && (Radius < Center.Distance(center1) + C1.Radius()))
              {
                pnttg1sol(NbrSol) = gp_Pnt2d(Center.XY() - Radius * dir1.XY());
              }
              else if ((Center.Distance(center1) < C1.Radius()) && (Radius < C1.Radius()))
              {
                pnttg1sol(NbrSol) = gp_Pnt2d(Center.XY() + Radius * dir1.XY());
              }
              else
              {
                pnttg1sol(NbrSol) = gp_Pnt2d(Center.XY() - Radius * dir1.XY());
              }
              pnttg2sol(NbrSol) = Point2;
            }
          }
          WellDone = true;
        }
      }
    }
    else if (nbsol < 0)
    {
      gp_Pnt2d Center(center1.XY() + deport * dir1.XY());
      cirsol(1) = gp_Circ2d(gp_Ax2d(Center, dirx), Radius);
      //    ==================================================
      qualifier1(1) = Qualified1.Qualifier();
      qualifier2(1) = GccEnt_noqualifier;
      if (std::abs(deport) <= Tol && std::abs(Radius - R1) <= Tol)
      {
        TheSame1(1) = 1;
      }
      else
      {
        pnttg1sol(1) = gp_Pnt2d(Center.XY() + signe * Radius * dir1.XY());
      }
      pnttg2sol(1) = Point2;
      WellDone     = true;
      NbrSol       = 1;
    }
  }
  for (i = 1; i <= NbrSol; i++)
  {
    par1sol(i) = ElCLib::Parameter(cirsol(i), pnttg1sol(i));
    if (TheSame1(i) == 0)
    {
      pararg1(i) = ElCLib::Parameter(C1, pnttg1sol(i));
    }
    par2sol(i) = ElCLib::Parameter(cirsol(i), pnttg2sol(i));
    pararg2(i) = 0.;
  }
}
