/**
   * Clip out a section of the arm, using internal parameter values
   *
   * @param c0 : start of section to clip out
   * @param c1 : end of section to clip out; if < c0, c0 & c1 are swapped
   */
  private void clip0(double c0, double c1) {
    final boolean db = false;

    if (c0 > c1) {
      double t = c0;
      c0 = c1;
      c1 = t;
    }

    // array to store new clip elements in
    DArray nc = new DArray();

    if (db) {
      System.out.println("clip " + Tools.f(c0) + "..." + Tools.f(c1));
    }

    for (int i = 0; i < visSeg.size(); i += 2) {

      double t0 = visSeg.getDouble(i), t1 = visSeg.getDouble(i + 1);
      if (db) {

        System.out.println(" seg " + i + " " + Tools.f(t0) + "..." + Tools.f(t1));
      }

      // if this seg is outside clip region, leave intact (add it)
      if (t0 >= c1 || t1 <= c0) {
        nc.addDouble(t0);
        nc.addDouble(t1);
        continue;
      }

      if (t0 >= c0 && t1 <= c1) {
        if (db) {
          System.out.println("  clipping entire segment");
        }
        // remove entire segment, by skipping it
        continue;
      }

      // add unclipped portion
      if (t0 < c0) {
        nc.addDouble(t0);
        nc.addDouble(c0);
      }
      if (t1 > c1) {
        nc.addDouble(c1);
        nc.addDouble(t1);
      }
    }
    visSeg = nc;
    if (db) {
      System.out.println(" after clipping:" + visSeg);
    }
  }
 /**
  * Calculate angle of tangent line at a particular point
  *
  * @param t : parameter
  * @return angle
  */
 public double calcDirectionAt(double t) {
   final boolean db = false;
   calcTangentAt(t, workPt);
   double a = Math.atan2(workPt.y, workPt.x);
   if (db) {
     System.out.println("calcDirectionAt " + Tools.f(t) + " a=" + Tools.fa(a));
   }
   return a;
 }
  /**
   * Construct a hyperbola
   *
   * @param f1 FPoint2 : first focus
   * @param f2 FPoint2 : second focus
   * @param interceptDistance : closest distance of point on arm to f1
   */
  public Hyperbola(FPoint2 f1, FPoint2 f2, double interceptDistance) {
    double fDist = f2.distance(f1);
    if (!(interceptDistance >= 0 && interceptDistance <= fDist))
      throw new FPError(
          "Hyperbola construction: icept="
              + Tools.f(interceptDistance)
              + " of max "
              + Tools.f(fDist)
              + "\n f1="
              + f1
              + " f2="
              + f2);

    double ratio = 0;
    if (fDist > 0) {
      ratio = interceptDistance / fDist;
    }
    FPoint2 pt = FPoint2.interpolate(f1, f2, ratio);
    construct(f1, f2, pt);
  }
  public String visString() {
    StringBuilder sb = new StringBuilder();
    if (visSeg != null) {
      for (int i = 0; i < visSeg.size(); i += 2) {
        double t0 = 0, t1 = 0;
        if (flipped()) {
          int j = visSeg.size() - i - 2;
          t0 = toExt(visSeg.getDouble(j + 1));
          t1 = toExt(visSeg.getDouble(j));
        } else {
          t0 = visSeg.getDouble(i);
          t1 = visSeg.getDouble(i + 1);
        }

        sb.append("<");
        sb.append(Tools.f(t0));
        sb.append("..");

        sb.append(Tools.f(t1));
        sb.append("> ");
      }
    }
    return sb.toString();
  }
  /**
   * Test program for Hyperbola class
   *
   * @param args String[]
   */
  public static void main(String[] args) {

    final double[] pts = { //
      100, 0, -100, 0, 75, 20,

      //  120, 30, -100, -10, 70, 50,
    };

    for (int i = 0; i < pts.length; i += 6) {
      try {
        Hyperbola h =
            new Hyperbola(
                new FPoint2(pts[i + 0], pts[i + 1]),
                new FPoint2(pts[i + 2], pts[i + 3]),
                new FPoint2(pts[i + 4], pts[i + 5]));
        System.out.println("Constructed:\n" + h);

        for (double t = -50; t <= 50; t += 10) {

          FPoint2 pt = h.calcPoint(t);

          FPoint2 pt2 = new FPoint2(pt.x, pt.y + 5);

          double tClosest = h.closestPointTo(pt2);

          System.out.println("t=" + Tools.f(t) + " pt=" + pt + " closest=" + tClosest);

          if (t == -20) {
            for (double t2 = tClosest - .1; t2 <= tClosest + .1; t2 += .01) {
              FPoint2 pt3 = h.calcPoint(t2);
              Streams.out.println("t2=" + t2 + " dist=" + pt3.distance(pt2));
            }
          }
        }

      } catch (TBError e) {
        System.out.println(e.toString());
      }
    }
  }
  /**
   * Calculate a point on the hyperbola
   *
   * @param t : parameter in internal space (after flipping has occurred)
   * @param dest : where to store the calculated point
   */
  private FPoint2 calcPointInternal(double t, FPoint2 dest) {
    final boolean db = false;
    if (!valid) throw new FPError("calcPoint of invalid hyperbola");

    //    Tools.ASSERT(valid, "calcPoint of invalid hyperbola");
    //    final FPoint2 work = new FPoint2();
    //
    //    work.setLocation(Polyn.sqrt(A + t * t * B), t);
    dest = toW2.apply(Polyn.sqrt(A + t * t * B), t, dest);
    //
    //    if (dest == null)
    //      dest = new FPoint2();
    //    Matrix.apply(toW2, work, dest);
    if (db) {
      System.out.println(
          "calcPoint t="
              + Tools.f(toExt(t)) // + " armsp=" + work
              + " world="
              + dest);
    }
    return dest;
  }
  private void construct(EdDisc a, EdDisc b) {
    this.discA = a;
    this.discB = b;

    if (EdDisc.partiallyDisjoint(a, b)) {

      // if (!UHullMain.oldBitanMethod())
      {
        final boolean db = false;

        if (a.getRadius() == b.getRadius()) {
          FPoint2 oa = a.getOrigin(), ob = b.getOrigin();

          FPoint2 n = new FPoint2(-(ob.y - oa.y), ob.x - oa.x);
          n.normalize();
          n.x *= a.getRadius();
          n.y *= a.getRadius();
          seg = new DirSeg(FPoint2.add(oa, n, null), FPoint2.add(ob, n, null));
          return;
        }

        boolean swap = a.getRadius() > b.getRadius();

        if (swap) {
          b = (EdDisc) discA;
          a = (EdDisc) discB;
        }

        if (db && T.update())
          T.msg(
              "BiTangent construct, arad="
                  + Tools.f(a.getRadius())
                  + " brad="
                  + Tools.f(b.getRadius())
                  + " swap="
                  + swap
                  + " origin.a="
                  + T.show(a.getOrigin()));

        FPoint2 oa = a.getOrigin();
        FPoint2 ob = b.getOrigin();

        double U = ob.x, V = ob.y;
        double A = oa.x - U, B = oa.y - V;
        double R1 = a.getRadius();
        double R2 = b.getRadius();
        double S = R2 - R1;

        double x1, y1, x2, y2;
        x1 = A;
        y1 = B;

        boolean secondRoot;
        boolean altSlope = Math.abs(B) < Math.abs(A);
        if (!altSlope) {

          double C1 = S * S / B, C2 = -A / B;
          double qA = 1 + C2 * C2, qB = 2 * C1 * C2, qC = C1 * C1 - S * S;
          double root = Math.sqrt(qB * qB - 4 * qA * qC);
          x2 = (-qB - root) / (2 * qA);
          y2 = C1 + C2 * x2;

          secondRoot = MyMath.sideOfLine(x2, y2, A, B, 0, 0) < 0;

          if (swap ^ secondRoot) {
            x2 = (-qB + root) / (2 * qA);
            y2 = C1 + C2 * x2;
          }
        } else {

          double C1 = S * S / A, C2 = -B / A;
          double qA = 1 + C2 * C2, qB = 2 * C1 * C2, qC = C1 * C1 - S * S;
          double root = Math.sqrt(qB * qB - 4 * qA * qC);
          y2 = (-qB - root) / (2 * qA);
          x2 = C1 + C2 * y2;

          secondRoot = MyMath.sideOfLine(x2, y2, A, B, 0, 0) < 0;

          if (swap ^ secondRoot) {
            y2 = (-qB + root) / (2 * qA);
            x2 = C1 + C2 * y2;
          }
        }
        // now grow both discs back to r1, r2

        double tx = U;
        double ty = V;

        //        if (S == 0) {
        //          FPoint2 unit = new FPoint2(-A, -B);
        //          if (swap) {
        //            unit.x = -unit.x;
        //            unit.y = -unit.y;
        //          }
        //          unit.normalize();
        //          tx += -unit.y * R1;
        //          ty += unit.x * R1;
        //        } else
        {
          double F = R1 / S;
          tx += x2 * F;
          ty += y2 * F;
        }

        if (db && T.update())
          T.msg("adding offset to both points: " + tx + ", " + ty + T.show(new FPoint2(tx, ty)));
        x1 += tx;
        y1 += ty;
        x2 += tx;
        y2 += ty;
        FPoint2 p1 = new FPoint2(x1, y1);
        FPoint2 p2 = new FPoint2(x2, y2);
        if (swap) {
          FPoint2 tmp = p1;
          p1 = p2;
          p2 = tmp;
        }

        seg = new DirSeg(p1, p2);

        if (db && T.update())
          T.msg(
              "swap="
                  + swap
                  + " altSlope="
                  + altSlope
                  + " secondRoot="
                  + secondRoot
                  + " dirseg="
                  + EdSegment.showDirected(p1, p2));
      }
      //      else {
      //
      //        double th = calcTheta(a, b);
      //        LineEqn eqn = new LineEqn(a.polarPoint(th + Math.PI / 2), th);
      //        double ta = eqn.parameterFor(a.getOrigin());
      //        double tb = eqn.parameterFor(b.getOrigin());
      //        seg = new DirSeg(eqn.pt(ta), eqn.pt(tb));
      //
      //      }
    }
  }
  /**
   * Construct a string
   *
   * @param javaMode : if true, generates java code to construct it
   * @return String
   */
  private String toString(boolean javaMode) {

    StringBuilder sb = new StringBuilder();
    //    sb.setLength(0);

    if (javaMode) {
      sb.append(
          "new Hyperbola(new FPoint2("
              + foci[RIGHT].x
              + ","
              + foci[RIGHT].y
              + "), new FPoint2("
              + foci[LEFT].x
              + ","
              + foci[LEFT].y
              + "), new FPoint2("
              + pt.x
              + ","
              + pt.y
              + "));");
    } else {
      sb.append("hyperbola: ");
      if (!valid) {
        sb.append("***INVALID***");
      } else {
        sb.append(
            "f(r)="
                + foci[RIGHT]
                + " f(l)="
                + foci[LEFT]
                + " a="
                + Tools.f(a)
                + " c="
                + Tools.f(c));
        sb.append(" flip=" + Tools.f(flipped()));
        if (visSeg != null) {
          sb.append("\nVisible segments: ");
          for (int i = 0; i < visSeg.size(); i += 2) {
            double t0 = 0, t1 = 0;
            if (flipped()) {
              int j = visSeg.size() - i - 2;
              t0 = toExt(visSeg.getDouble(j + 1));
              t1 = toExt(visSeg.getDouble(j));
            } else {
              t0 = visSeg.getDouble(i);
              t1 = visSeg.getDouble(i + 1);
            }

            sb.append("<");
            sb.append(Tools.f(t0));
            sb.append("..");

            sb.append(Tools.f(t1));
            sb.append("> ");
          }
          sb.append("\n");
        }
      }
    }
    return sb.toString();
  }
  /**
   * 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();
      }
    }
  }