// Created on: 1993-01-27
// Created by: Isabelle GRIGNON
// Copyright (c) 1993-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 <Adaptor2d_Curve2d.hxx>
#include <gp_Pnt2d.hxx>
#include <IntPatch_PolyArc.hxx>
#include <Standard_ConstructionError.hxx>

inline void MinMax(const double a1, const double a2, double& amin, double& amax)
{
  if (a1 < a2)
  {
    amin = a1;
    amax = a2;
  }
  else
  {
    amin = a2;
    amax = a1;
  }
}

IntPatch_PolyArc::IntPatch_PolyArc(const occ::handle<Adaptor2d_Curve2d>& Line,
                                   const int                             NbSample,
                                   const double                          aPdeb,
                                   const double                          aPfin,
                                   const Bnd_Box2d&                      BoxOtherPolygon)
    : brise(1, std::max(1, NbSample)),
      param(1, std::max(1, NbSample)),
      offsetx(0.0),
      offsety(0.0)
{
  double   Pdeb = aPdeb;
  double   Pfin = aPfin;
  gp_Pnt2d p2d;

  if (Pdeb == RealFirst() || Pfin == RealLast() || NbSample <= 1)
  {
    throw Standard_ConstructionError();
  }
  //----------------------------------------------------------------------
  //-- On veut eviter les cas ou  le present polygone est beaucoup plus
  //-- grand que l objet en second.
  //--
  //-- Par exemple lorsque l objet en second est une ligne de cheminement
  //-- qui contient de nombreux segments (>>100), une fleche nulle
  //-- et ce polygone quelques segments et une fleche qui contient
  //-- toute la ligne de cheminement.
  //--
  //-- Dans ce cas (tout un polygone compris dans la zone d influence)
  //-- les calculs deviennent tres longs (N2)
  //----------------------------------------------------------------------
  int IndexInf = NbSample + 1;
  int IndexSup = 0;

  double bx0, by0, bxmin, bxmax, bymin, bymax, r, r2;

  BoxOtherPolygon.Get(bxmin, bymin, bxmax, bymax);
  r   = (bxmax - bxmin) + (bymax - bymin);
  bx0 = (bxmax + bxmin) * 0.5;
  by0 = (bymax + bymin) * 0.5;

  double Pas;
  double X, Y, Xs, Ys, Xm, Ym, XXs, YYs;

  r *= 0.8;
  r2         = r * r * 49.;
  int nbloop = 0;

  do
  {
    nbloop++;
    Pas      = (Pfin - Pdeb) / (NbSample - 1);
    param(1) = Pdeb;
    Line->D0(Pdeb, p2d);
    Xs = p2d.X();
    Ys = p2d.Y();
    brise(1).SetCoord(Xs, Ys);

    myBox.SetVoid();

    myBox.Add(brise(1));
    myError = 0.;

    for (int i = 2; i <= NbSample; i++)
    {
      param(i) = Pdeb + (i - 1) * Pas;
      Line->D0(param(i), p2d);
      X = p2d.X();
      Y = p2d.Y();
      brise(i).SetCoord(X, Y);
      XXs = 0.5 * (Xs + X);
      YYs = 0.5 * (Ys + Y);
      //------------------------------------------------------------
      //-- On recherche le debut et la fin de la zone significative
      //------------------------------------------------------------
      // MSV: (see cda 002 H2) if segment is too large (>>r) we have
      //      a risk to jump through BoxOtherPolygon, therefore we should
      //      check this condition if the first one is failure.
      bool isMidPtInBox = (std::abs(bx0 - XXs) + std::abs(by0 - YYs)) < r;
      bool isSegOut     = true;
      if (!isMidPtInBox)
      {
        double d = (X - Xs) * (X - Xs) + (Y - Ys) * (Y - Ys);
        if (d > r2)
        {
          double xmin, xmax, ymin, ymax;
          MinMax(Xs, X, xmin, xmax);
          MinMax(Ys, Y, ymin, ymax);
          isSegOut = (xmax < bxmin || xmin > bxmax || ymax < bymin || ymin > bymax);
        }
      }
      if (isMidPtInBox || !isSegOut)
      {
        // MSV: take the previous and the next segments too, because of
        //      we check only the middle point (see BUC60946)
        // if(IndexInf>i) IndexInf=i-1;
        // if(IndexSup<i) IndexSup=i;
        if (IndexInf > i)
          IndexInf = std::max(i - 2, 1);
        if (IndexSup < i)
          IndexSup = std::min(i + 1, NbSample);
      }

      myBox.Add(brise(i));
      Line->D0(param(i) - Pas * 0.5, p2d);
      Xm      = p2d.X() - XXs;
      Ym      = p2d.Y() - YYs;
      Xm      = std::sqrt(Xm * Xm + Ym * Ym);
      myError = std::max(myError, Xm);
      Xs      = X;
      Ys      = Y;
    }
    if (IndexInf > IndexSup)
    {
      r += r;
      r2 = r * r * 49.;
      //-- std::cout<<" Le rayon : "<<r<<" est insuffisant "<<std::endl;
    }
    else
    {
      //----------------------------------------------
      //-- Si le nombre de points significatifs est
      //-- insuffisant, on reechantillonne une fois
      //-- encore
      //----------------------------------------------
      if ((IndexSup - IndexInf) < (NbSample / 2))
      {
        //-- std::cout<<" --- On remet ca entre les index "<<IndexInf<<" et "<<IndexSup<<std::endl;
        nbloop = 10;
        // if(IndexInf>1) IndexInf--;
        // if(IndexSup<NbSample) IndexSup++;
        Pdeb = param(IndexInf);
        Pfin = param(IndexSup);
        // IndexInf = IndexSup+1;
        IndexInf = NbSample + 1;
        IndexSup = 0;
      }
    }
  } while ((IndexInf > IndexSup) && nbloop <= 10);
  myError *= 1.2;
  if (myError < 0.00000001)
    myError = 0.00000001;
  myBox.Enlarge(myError);

  ferme = (Line->Value(aPdeb).Distance(Line->Value(aPfin)) <= 1e-7);
}

bool IntPatch_PolyArc::Closed() const
{
  return ferme;
}

int IntPatch_PolyArc::NbPoints() const
{
  return brise.Length();
}

gp_Pnt2d IntPatch_PolyArc::Point(const int Index) const
{
  if (offsetx == 0.0 && offsety == 0.0)
    return (brise(Index));

  const gp_Pnt2d& P = brise(Index);
  return (gp_Pnt2d(P.X() + offsetx, P.Y() + offsety));
}

double IntPatch_PolyArc::Parameter(const int Index) const
{
  return param(Index);
}

void IntPatch_PolyArc::SetOffset(const double ox, const double oy)
{
  double xmin, ymin, xmax, ymax, g;
  myBox.Get(xmin, ymin, xmax, ymax);
  g = myBox.GetGap();

  myBox.SetVoid();

  myBox.Update(xmin - offsetx, ymin - offsety, xmax - offsetx, ymax - offsety);
  offsetx = ox;
  offsety = oy;
  myBox.Update(xmin + offsetx, ymin + offsety, xmax + offsetx, ymax + offsety);
  myBox.SetGap(g);
}
