// SPDX-FileCopyrightText: 2004 Pino Toscano <toscano.pino@tiscali.it>

// SPDX-License-Identifier: GPL-2.0-or-later

#include "tangent_type.h"

#include "bogus_imp.h"
#include "conic_imp.h"
#include "cubic_imp.h"
#include "curve_imp.h"
#include "other_imp.h"
#include "point_imp.h"
#include "line_imp.h"

#include "../misc/common.h"
#include "../misc/conic-common.h"
//#include "../misc/calcpaths.h"
#include "../kig/kig_part.h"
#include "../kig/kig_view.h"

static const char constructlinetangentpoint[] = "SHOULDNOTBESEEN";
static const char selecttangent1[] = 
  I18N_NOOP( "Select the curve..." );
static const char selecttangent2[] = 
  I18N_NOOP( "Select the point for the tangent to go through..." );

static const ArgsParser::spec argsspecTangentConic[] =
{
  { ConicImp::stype(), "SHOULDNOTBESEEN", selecttangent1, false },
  { PointImp::stype(), constructlinetangentpoint, selecttangent2, true }
};

KIG_INSTANTIATE_OBJECT_TYPE_INSTANCE( TangentConicType )

TangentConicType::TangentConicType()
  : ArgsParserObjectType( "TangentConic", argsspecTangentConic, 2 )
{
}

TangentConicType::~TangentConicType()
{
}

const TangentConicType* TangentConicType::instance()
{
  static const TangentConicType t;
  return &t;
}

ObjectImp* TangentConicType::calc( const Args& args, const KigDocument& doc ) const
{
  if ( !margsparser.checkArgs( args ) )
    return new InvalidImp;

  const ConicImp* c = static_cast<const ConicImp*>( args[0] );
  const Coordinate& p = static_cast<const PointImp*>( args[1] )->coordinate();

  if ( !c->containsPoint( p, doc ) )
    return new InvalidImp;

  bool ok;
  const LineData tangent = calcConicPolarLine( c->cartesianData(), p, ok );
  
  if ( !ok )
    return new InvalidImp;
  
  return new LineImp( tangent );
}

const ObjectImpType* TangentConicType::resultId() const
{
  return LineImp::stype();
}

/*** Arc starts here ***/

static const ArgsParser::spec argsspecTangentArc[] =
{
  { ArcImp::stype(), "SHOULDNOTBESEEN", selecttangent1, false },
  { PointImp::stype(), constructlinetangentpoint, selecttangent2, true }
};

KIG_INSTANTIATE_OBJECT_TYPE_INSTANCE( TangentArcType )

TangentArcType::TangentArcType()
  : ArgsParserObjectType( "TangentArc", argsspecTangentArc, 2 )
{
}

TangentArcType::~TangentArcType()
{
}

const TangentArcType* TangentArcType::instance()
{
  static const TangentArcType t;
  return &t;
}

ObjectImp* TangentArcType::calc( const Args& args, const KigDocument& doc ) const
{
  if ( !margsparser.checkArgs( args ) )
    return new InvalidImp;

  const ArcImp* arc = static_cast<const ArcImp*>( args[0] );
  const Coordinate& p = static_cast<const PointImp*>( args[1] )->coordinate();

  if ( !arc->containsPoint( p, doc ) )
    return new InvalidImp;

  Coordinate c = arc->center();
  double sqr = arc->radius();
  sqr *= sqr;
  ConicCartesianData data( 1.0, 1.0, 0.0, -2*c.x, -2*c.y, c.x*c.x + c.y*c.y - sqr );

  bool ok;
  const LineData tangent = calcConicPolarLine( data, p, ok );
  
  if ( !ok )
    return new InvalidImp;

  return new LineImp( tangent );
}

const ObjectImpType* TangentArcType::resultId() const
{
  return LineImp::stype();
}

/**** Cubic starts here ****/

static const ArgsParser::spec argsspecTangentCubic[] =
{
  { CubicImp::stype(), "SHOULDNOTBESEEN", selecttangent1, false },
  { PointImp::stype(), constructlinetangentpoint, selecttangent2, true }
};

KIG_INSTANTIATE_OBJECT_TYPE_INSTANCE( TangentCubicType )

TangentCubicType::TangentCubicType()
  : ArgsParserObjectType( "TangentCubic", argsspecTangentCubic, 2 )
{
}

TangentCubicType::~TangentCubicType()
{
}

const TangentCubicType* TangentCubicType::instance()
{
  static const TangentCubicType t;
  return &t;
}

ObjectImp* TangentCubicType::calc( const Args& args, const KigDocument& doc ) const
{
  if ( !margsparser.checkArgs( args ) )
    return new InvalidImp;

  const CubicImp* cubic = static_cast<const CubicImp*>( args[0] );
  const Coordinate& p = static_cast<const PointImp*>( args[1] )->coordinate();

  if ( !cubic->containsPoint( p, doc ) )
    return new InvalidImp;

  double x = p.x;
  double y = p.y;
  CubicCartesianData data = cubic->data();
//  double aconst = data.coeffs[0];
  double ax = data.coeffs[1];
  double ay = data.coeffs[2];
  double axx = data.coeffs[3];
  double axy = data.coeffs[4];
  double ayy = data.coeffs[5];
  double axxx = data.coeffs[6];
  double axxy = data.coeffs[7];
  double axyy = data.coeffs[8];
  double ayyy = data.coeffs[9];

  /* mp: the tangent vector (-gy,gx) is orthogonal to the gradient (gx,gy)
   * which is easy to compute from the CartesianData
   *
   * note: same thing could be done for conics, which would be
   * much more efficient...
   */
     
  Coordinate tangvec = Coordinate (
      - axxy*x*x - 2*axyy*x*y - 3*ayyy*y*y - axy*x - 2*ayy*y - ay,
      3*axxx*x*x + 2*axxy*x*y + axyy*y*y + 2*axx*x + axy*y + ax
                                   );
  const LineData tangent = LineData( p, p + tangvec );
 
  return new LineImp( tangent );
}

const ObjectImpType* TangentCubicType::resultId() const
{
  return LineImp::stype();
}

/**** Curve (locus) starts here ****/

static const ArgsParser::spec argsspecTangentCurve[] =
{
  { CurveImp::stype(), "SHOULDNOTBESEEN", selecttangent1, false },
  { PointImp::stype(), constructlinetangentpoint, selecttangent2, true }
};

KIG_INSTANTIATE_OBJECT_TYPE_INSTANCE( TangentCurveType )

TangentCurveType::TangentCurveType()
  : ArgsParserObjectType( "TangentCurve", argsspecTangentCurve, 2 )
{
}

TangentCurveType::~TangentCurveType()
{
}

const TangentCurveType* TangentCurveType::instance()
{
  static const TangentCurveType t;
  return &t;
}

ObjectImp* TangentCurveType::calc( const Args& args, const KigDocument& doc ) const
{
  if ( !margsparser.checkArgs( args ) )
    return new InvalidImp;

  const CurveImp* curve = static_cast<const CurveImp*>( args[0] );
  const Coordinate& p = static_cast<const PointImp*>( args[1] )->coordinate();
  if ( !curve->containsPoint( p, doc ) )
    return new InvalidImp;

  const double t = curve->getParam( p, doc );
  const double tau0 = 1e-3;
  const double sigma = 1e-5;
  const int maxiter = 20;

  double tau = tau0;
  Coordinate tang, err;
  double tplus = t + tau;
  double tminus = t - tau;
  if ( tplus > 1 ) {tplus = 1; tminus = 1 - 2*tau;}
  if ( tminus < 0 ) {tminus = 0; tplus = 2*tau;}
  Coordinate tangold = (curve->getPoint( tplus, doc ) - curve->getPoint( tminus, doc ))/(2*tau);

  for (int i = 0; i < maxiter; i++)
  {
    tau = tau/2;
    tplus = t + tau;
    tminus = t - tau;
    if ( tplus > 1 ) {tplus = 1; tminus = 1 - 2*tau;}
    if ( tminus < 0 ) {tminus = 0; tplus = 2*tau;}
    tang = (curve->getPoint( tplus, doc ) - curve->getPoint( tminus, doc ))/(2*tau);
    err = (tangold - tang)/3;
    if (err.length() < sigma)
    {
      tang = (4*tang - tangold)/3;
      const LineData tangent = LineData( p, p + tang );
      return new LineImp( tangent );
    }
    tangold = tang;
  }
  return new InvalidImp;
}

const ObjectImpType* TangentCurveType::resultId() const
{
  return LineImp::stype();
}
