/**
   * Determine closest point on hyperbolic arm to a point pt
   *
   * @param pt : point
   * @return t value for closest point; not necessarily within clipped range
   */
  public double closestPointTo(FPoint2 pt) {

    final boolean db = false;

    pt = toCurveSpace(pt, null);

    double U = B + 1;
    double V = -pt.y;
    double W = B * pt.x;

    Polyn p =
        new Polyn( //
            B * U * U, //
            2 * B * U * V, //
            B * V * V + A * U * U - W * W, //
            2 * A * U * V, //
            A * V * V //
            );

    if (db) Streams.out.println("closestPointTo, pt=" + pt + "\n" + p);
    double ret = 0;

    try {
      DArray r = new DArray();

      if (Math.abs(p.c(0)) < 1e-5) r.addDouble(0);
      else p.solve(r);

      if (r.isEmpty()) {
        throw new FPError("can't find closest point, poly=\n" + p);
      }

      double bestDist = 0;

      for (int i = 0; i < r.size(); i++) {
        double t = r.getDouble(i);
        FPoint2 apt = calcPoint(t);
        double dist = apt.distance(pt);
        if (i == 0 || dist < bestDist) {
          bestDist = dist;
          ret = t;
        }
      }
    } catch (FPError e) {
      Tools.warn("caught FPError");
      //      Streams.out.println("caught:\n" + e);
      ret = (this.minParameter() + this.maxParameter()) * .5;
    }

    return ret;
  }
  /**
   * Find intersections of hyperbolic arm with a line.
   *
   * @param s0 : first point on line
   * @param s1 : second point on line
   * @param ipts : intersection points returned here
   */
  public DArray findLineIntersect(FPoint2 s0, FPoint2 s1, DArray ipts) {
    if (ipts == null) ipts = new DArray();
    ipts.clear();

    // transform both line points to curve space

    FPoint2 c0 = toCurveSpace(s0, null), c1 = toCurveSpace(s1, null);

    double a = c0.x, b = c0.y, c = c1.x, d = c1.y;

    double e = d - b;
    double f = c - a;

    double D = f * f - B * e * e;
    double E = 2 * a * f - 2 * b * B * e;
    double F = a * a - A - B * b * b;
    Polyn q = new Polyn(D, E, F);

    DArray roots = new DArray();
    q.solve(roots);

    for (int i = 0; i < roots.size(); i++) {
      double k = roots.getDouble(i);
      FPoint2 pt =
          //      ipts.add(
          new FPoint2(s0.x + (s1.x - s0.x) * k, s0.y + (s1.y - s0.y) * k);
      //      );

      // Make sure this point is actually on the arm.
      FPoint2 cpt = toCurveSpace(pt, null);
      if (cpt.x < 0) {
        continue;
      }
      ipts.add(pt);
    }
    return ipts;
  }
  /**
   * Find intersections of hyperbolic arm with an axes-aligned line segment. Arm must intersect
   * segment, not its underlying (infinite) line. The intersection points are sorted into increasing
   * parameter values w.r.t. the arm.
   *
   * @param s0 : start of line segment
   * @param s1 : end of line segment
   * @param ipts : intersection points returned here
   * @param reverseOrder : if true, points are sorted by decreasing parameter values
   * @param dbFlag : true to print debug information
   */
  public void findOrthogonalIntersect(
      FPoint2 s0, FPoint2 s1, DArray ipts, boolean reverseOrder, boolean dbFlag) {

    final boolean db = true && dbFlag;

    if (db) {
      System.out.println(
          "findOrthogonalIntersect " + s0 + " -> " + s1 + " rev:" + Tools.f(reverseOrder));
    }

    // Determine whether this is a vertical or horizontal line segment.

    boolean vert = (Math.abs(s1.y - s0.y) > Math.abs(s1.x - s0.x));
    if (db) {
      System.out.println(" vert=" + Tools.f(vert));
    }

    // Find the quadratic to solve.
    Polyn p;
    PlaneCurve cv = getCurve();
    if (vert) {
      p = cv.solveForX(s0.x);
    } else {
      p = cv.solveForY(s0.y);
    }

    DArray lst = new DArray();
    p.solve(lst);

    if (db) {
      System.out.println(" curve=" + cv.toString(true));
      System.out.println(" polyn=" + p.toString(true));
      System.out.println(" roots=" + Tools.d(lst));
    }

    // Sort points, discarding those not on line segment,
    // and those not on the correct arm.
    ipts.clear();

    for (int i = 0; i < lst.size(); i++) {
      double ta = lst.getDouble(i);
      FPoint2 pt;
      if (vert) {
        pt = new FPoint2(s0.x, ta);
      } else {
        pt = new FPoint2(ta, s0.y);
      }

      if (db) {
        System.out.println(" position on arm for ta=" + ta + " is " + pt);
      }
      double t = MyMath.positionOnSegment(pt, s0, s1);
      if (db) {
        System.out.println("  pt=" + pt + " t=" + t);
      }
      if (t < 0 || t > 1) {
        if (db) {
          System.out.println("   not on segment, skipping");
        }
        continue;
      }

      FPoint2 cpt = toCurveSpace(pt, null);
      if (db) {
        System.out.println("  curveSpace=" + cpt);
      }
      if (cpt.x < 0) {
        if (db) {
          System.out.println("   skipping...");
        }
        continue;
      }

      double t1 = calcParameter(pt);
      int j = 0;
      while (true) {
        if (j == ipts.size()) {
          break;
        }
        double t2 = calcParameter(ipts.getFPoint2(j));
        if (!reverseOrder) {
          if (t1 < t2) {
            break;
          }
        } else if (t1 > t2) {
          break;
        }
        j++;
      }
      ipts.add(j, pt);
    }
    if (db) {
      System.out.println(" ipts=" + Tools.d(ipts));
    }
  }
  public void render(Color c, int stroke, int markType) {
    final boolean db = false;
    // Get array of visible segments.  If no such array exists,
    // use default.
    DArray vseg = visSeg;
    boolean dashed = false;

    //    if (step == 0) {
    double step = renderStep();
    //    }
    //    vp V = TestBed.view();

    if (db) Streams.out.println(" step=" + step);

    // plot each visible segment

    for (int seg = 0; seg < vseg.size(); seg += 2) {
      double t0 = vseg.getDouble(seg + 0), t1 = vseg.getDouble(seg + 1);
      t0 = MyMath.clamp(t0, -500.0, 500.0);
      t1 = MyMath.clamp(t1, -500.0, 500.0);

      // render() expects external parameters.

      double s0 = toExt(t0), s1 = toExt(t1);
      if (s0 > s1) {
        double tmp = s0;
        s0 = s1;
        s1 = tmp;
      }
      FPoint2 p0 = calcPoint(s0), p1 = calcPoint(s1);
      if (db) Streams.out.println(" p0=" + p0 + ", p1=" + p1);
      if (isLine() && !dashed) {
        V.drawLine(p0, p1);
      } else {

        /*
                  if (Math.abs(s0) >= 500
           ||Math.abs(s1) >= 500)
         System.out.println("Rendering "+t0+" to "+t1+" step "+step);
        */
        if (dashed) V.pushStroke(Globals.STRK_RUBBERBAND);
        {
          //          int count = 0;
          boolean first = true;
          for (double t = t0; ; t += step) { // , count++) {
            boolean last = (t >= t1);
            if (last) {
              t = t1;
            }
            calcPointInternal(t, p1);

            if (!p1.isValid()) {
              if (last) {
                break;
              }
              continue;
            }

            if (db) {
              System.out.println(" calcPt " + Tools.f(toExt(t)) + " = " + p1.x + "," + p1.y);
            }
            if (!first) {
              V.drawLine(p0, p1);
              if (false) {
                Tools.warn("highlighting int");
                V.mark(p0);
              }
            }
            if (last) {
              break;
            }
            p0.setLocation(p1);
            first = false;
          }
        }
        if (dashed) V.popStroke();
      }
    }
  }
  /**
   * Find intersections between two hyperbolas
   *
   * @param a Hyperbola
   * @param b Hyperbola
   * @param iPts where to store intersection points; null to construct
   */
  public static DArray findIntersections(Hyperbola a, Hyperbola b, DArray iPts) {
    final boolean db = false;

    if (iPts == null) iPts = new DArray();
    iPts.clear();
    final DArray jPts = new DArray();
    // a.initClipIfNec();
    //    b.initClipIfNec();
    if (db) {
      System.out.println(
          "finding intersections between:\n" + a.toString(true) + "\n" + b.toString(true));
    }

    PlaneCurve.findIntersect(a.getCurve(), b.getCurve(), jPts);

    // filter out false intersections
    if (db) {
      System.out.println("prefilter # intersections= " + jPts.size());
    }

    FPoint2 ptc = new FPoint2();

    for (int i = 0; i < jPts.size(); i++) {
      FPoint2 pt = jPts.getFPoint2(i);

      // make sure calculated intersect point is actually on both arms
      {
        FPoint2 data = a.calcParameterAndDistance(pt);
        if (data.y > .001) continue;
        data = b.calcParameterAndDistance(pt);
        if (data.y > .001) continue;
      }

      if (!a.isLine()) {
        // put in curve space to verify it's to the right of the y axis
        a.toCurveSpace(pt, ptc);
        if (db) {
          System.out.println("Filter " + i + " in curveA= " + ptc);
        }
        if (ptc.x <= 0) {
          if (db) {
            System.out.println(" < 0");
          }
          continue;
        }
      }
      if (!b.isLine()) {
        b.toCurveSpace(pt, ptc);
        if (db) {
          System.out.println("        in curveB= " + ptc);
        }
        if (ptc.x <= 0) {
          if (db) {
            System.out.println(" < 0");
          }
          continue;
        }
      }
      if (db) {
        System.out.println(" adding " + pt);
      }
      iPts.add(pt);
    }
    return iPts;
  }
  /**
   * Constructor
   *
   * @param f1 FPoint2
   * @param f2 FPoint2
   * @param pt FPoint2, or null for bisector
   */
  private void construct(FPoint2 f1, FPoint2 f2, FPoint2 pt) {

    //    userData[LEFT] = new DArray();
    //    userData[RIGHT]  =new DArray();

    final boolean db = false;
    if (db) {
      System.out.println("Hyperbola constructor\n f1=" + f1 + "\n f2=" + f2 + "\n pt=" + pt);
    }
    boolean bisector = (pt == null);
    initializeVisibleSegments();

    // if point on arm is closer to f2 than f1, swap f1 & f2.

    if (!bisector && FPoint2.distanceSquared(f1, pt) > FPoint2.distanceSquared(f2, pt)) {
      flipped = true;
    }

    this.foci[RIGHT] = new FPoint2(f1);
    this.foci[LEFT] = new FPoint2(f2);
    if (!bisector) {
      this.pt = new FPoint2(pt);
    }

    double fociDist = FPoint2.distance(f1, f2);
    if (fociDist == 0) {
      throw new FPError("Hyperbola foci are same point");
    }

    c = fociDist * .5;

    // calculate the translation of the hyperbola away from
    // standard position.

    FPoint2 rFocus = getFocus(0), lFocus = getFocus(1);

    origin = new FPoint2(.5 * (rFocus.x + lFocus.x), .5 * (rFocus.y + lFocus.y));

    // calculate the angle of rotation of the hyperbola away
    // from the standard position.

    double theta = Math.atan2(rFocus.y - lFocus.y, rFocus.x - lFocus.x);

    Matrix fromCenterInW = Matrix.getTranslate(origin, true);
    Matrix rotToE = Matrix.getRotate(-theta);

    toE2 = rotToE;
    Matrix.mult(toE2, fromCenterInW, toE2);
    // calculate inverse

    toW2 = toE2.invert(null);

    //      Matrix toCenterInW = Matrix.translationMatrix(origin, false);
    //      Matrix rotToW = Matrix.getRotate2D(theta);
    //
    //      toW2 = toCenterInW;
    //      Matrix.mult(toW2, rotToW, toW2);
    //      Tools.warn("just invert matrix here");
    //

    if (bisector) {
      valid = true;
    } else {
      // get the arm point in hyperbola space.
      FPoint2 workPt = toE2.apply(pt, null);

      double xs = workPt.x * workPt.x;
      double cs = c * c;

      Polyn q = new Polyn(1, -(cs + xs + workPt.y * workPt.y), cs * xs);
      if (db) {
        System.out.println("a2 quadratic:\n" + q);
      }
      final DArray qsoln = new DArray();
      q.solve(qsoln);
      if (db) {
        Streams.out.println(qsoln);
      }
      double val = q.c(1) * -.5;
      int ql = qsoln.size();
      if (ql >= 1) {
        val = qsoln.getDouble(0);
      }

      // choose the root that is less than c*c.

      if (ql == 2) {
        if (val > qsoln.getDouble(1)) {
          val = qsoln.getDouble(1);
          if (db) {
            System.out.println(" two roots, choosing smaller.");
          }
        }
      }
      if (db) {
        System.out.println(" root chosen=" + val);
      }

      a = Polyn.sqrt(val);
      A = a * a;
      B = A / (c * c - A);
    }
    valid = true;
    if (db) {
      System.out.println(" ==> " + this);
    }
  }