// The arc shape of d3-shape (`arc()`), as vega-scenegraph uses it for arc marks: circular and annular
// sectors, with `padAngle` and rounded corners. A port of d3-shape 3 `src/arc.js` (ISC). The angles are
// in radians, clockwise from twelve o'clock. Trigonometry is V8's (see `@util.v8_sin`): the digits of
// the path depend on it.

///|
let arc_epsilon : Double = 1.0e-12

///|
let arc_pi : Double = 3.141592653589793

///|
let arc_half_pi : Double = 1.5707963267948966

///|
let arc_tau : Double = 6.283185307179586

///|
fn arc_acos(x : Double) -> Double {
  if x > 1.0 {
    0.0
  } else if x < -1.0 {
    arc_pi
  } else {
    @util.v8_acos(x)
  }
}

///|
fn arc_asin(x : Double) -> Double {
  if x >= 1.0 {
    arc_half_pi
  } else if x <= -1.0 {
    -arc_half_pi
  } else {
    @util.v8_asin(x)
  }
}

///|
fn arc_intersect(
  x0 : Double,
  y0 : Double,
  x1 : Double,
  y1 : Double,
  x2 : Double,
  y2 : Double,
  x3 : Double,
  y3 : Double,
) -> (Double, Double)? {
  let x10 = x1 - x0
  let y10 = y1 - y0
  let x32 = x3 - x2
  let y32 = y3 - y2
  let t = y32 * x10 - x32 * y10
  if t * t < arc_epsilon {
    return None
  }
  let t2 = (x32 * (y0 - y2) - y32 * (x0 - x2)) / t
  Some((x0 + t2 * x10, y0 + t2 * y10))
}

///|
priv struct CornerTangents {
  cx : Double
  cy : Double
  x01 : Double
  y01 : Double
  x11 : Double
  y11 : Double
}

///|
/// The perpendicular offset line of length `rc` (d3-shape `cornerTangents`).
fn corner_tangents(
  x0 : Double,
  y0 : Double,
  x1 : Double,
  y1 : Double,
  r1 : Double,
  rc : Double,
  cw : Bool,
) -> CornerTangents {
  let x01 = x0 - x1
  let y01 = y0 - y1
  let lo = (if cw { rc } else { -rc }) / (x01 * x01 + y01 * y01).sqrt()
  let ox = lo * y01
  let oy = -lo * x01
  let x11 = x0 + ox
  let y11 = y0 + oy
  let x10 = x1 + ox
  let y10 = y1 + oy
  let x00 = (x11 + x10) / 2.0
  let y00 = (y11 + y10) / 2.0
  let dx = x10 - x11
  let dy = y10 - y11
  let d2 = dx * dx + dy * dy
  let r = r1 - rc
  let dd = x11 * y10 - x10 * y11
  let d = (if dy < 0.0 { -1.0 } else { 1.0 }) *
    @util.js_max(0.0, r * r * d2 - dd * dd).sqrt()
  let mut cx0 = (dd * dy - dx * d) / d2
  let mut cy0 = (-dd * dx - dy * d) / d2
  let cx1 = (dd * dy + dx * d) / d2
  let cy1 = (-dd * dx + dy * d) / d2
  let dx0 = cx0 - x00
  let dy0 = cy0 - y00
  let dx1 = cx1 - x00
  let dy1 = cy1 - y00
  // pick the closer of the two intersection points
  if dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1 {
    cx0 = cx1
    cy0 = cy1
  }
  {
    cx: cx0,
    cy: cy0,
    x01: -ox,
    y01: -oy,
    x11: cx0 * (r1 / r - 1.0),
    y11: cy0 * (r1 / r - 1.0),
  }
}

///|
/// Draw the arc of an arc item (`startAngle`, `endAngle`, `padAngle`, `innerRadius`,
/// `outerRadius`, `cornerRadius`) around the origin.
pub fn draw_arc(ctx : Ctx, item : Item) -> Unit {
  let mut r0 = item.num0("innerRadius")
  let mut r1 = item.num0("outerRadius")
  let a0 = item.num0("startAngle") - arc_half_pi
  let a1 = item.num0("endAngle") - arc_half_pi
  let da = (a1 - a0).abs()
  let cw = a1 > a0
  // the outer radius is always the larger one
  if r1 < r0 {
    let r = r1
    r1 = r0
    r0 = r
  }
  if !(r1 > arc_epsilon) {
    // a point
    ctx.move_to(0.0, 0.0)
  } else if da > arc_tau - arc_epsilon {
    // a circle or an annulus
    ctx.move_to(r1 * @util.v8_cos(a0), r1 * @util.v8_sin(a0))
    ctx.arc(0.0, 0.0, r1, a0, a1, !cw)
    if r0 > arc_epsilon {
      ctx.move_to(r0 * @util.v8_cos(a1), r0 * @util.v8_sin(a1))
      ctx.arc(0.0, 0.0, r0, a1, a0, cw)
    }
  } else {
    // a circular or annular sector
    let mut a01 = a0
    let mut a11 = a1
    let mut a00 = a0
    let mut a10 = a1
    let mut da0 = da
    let mut da1 = da
    let ap = item.num0("padAngle") / 2.0
    let rp = if ap > arc_epsilon { (r0 * r0 + r1 * r1).sqrt() } else { 0.0 }
    let rc = @util.js_min((r1 - r0).abs() / 2.0, item.num0("cornerRadius"))
    let mut rc0 = rc
    let mut rc1 = rc
    // apply padding? since r1 >= r0, da1 >= da0
    if rp > arc_epsilon {
      let mut p0 = arc_asin(rp / r0 * @util.v8_sin(ap))
      let mut p1 = arc_asin(rp / r1 * @util.v8_sin(ap))
      da0 = da0 - p0 * 2.0
      if da0 > arc_epsilon {
        p0 = p0 * (if cw { 1.0 } else { -1.0 })
        a00 = a00 + p0
        a10 = a10 - p0
      } else {
        da0 = 0.0
        a00 = (a0 + a1) / 2.0
        a10 = a00
      }
      da1 = da1 - p1 * 2.0
      if da1 > arc_epsilon {
        p1 = p1 * (if cw { 1.0 } else { -1.0 })
        a01 = a01 + p1
        a11 = a11 - p1
      } else {
        da1 = 0.0
        a01 = (a0 + a1) / 2.0
        a11 = a01
      }
    }
    let x01 = r1 * @util.v8_cos(a01)
    let y01 = r1 * @util.v8_sin(a01)
    let x10 = r0 * @util.v8_cos(a10)
    let y10 = r0 * @util.v8_sin(a10)
    let mut x11 = 0.0
    let mut y11 = 0.0
    let mut x00 = 0.0
    let mut y00 = 0.0
    // rounded corners?
    if rc > arc_epsilon {
      x11 = r1 * @util.v8_cos(a11)
      y11 = r1 * @util.v8_sin(a11)
      x00 = r0 * @util.v8_cos(a00)
      y00 = r0 * @util.v8_sin(a00)
      // restrict the corner radius according to the sector angle; if this intersection
      // fails the arc is probably too small, so the corner radius is disabled
      if da < arc_pi {
        match arc_intersect(x01, y01, x00, y00, x11, y11, x10, y10) {
          Some((ox, oy)) => {
            let ax = x01 - ox
            let ay = y01 - oy
            let bx = x11 - ox
            let by = y11 - oy
            let kc = 1.0 /
              @util.v8_sin(
                arc_acos(
                  (ax * bx + ay * by) /
                  ((ax * ax + ay * ay).sqrt() * (bx * bx + by * by).sqrt()),
                ) /
                2.0,
              )
            let lc = (ox * ox + oy * oy).sqrt()
            rc0 = @util.js_min(rc, (r0 - lc) / (kc - 1.0))
            rc1 = @util.js_min(rc, (r1 - lc) / (kc + 1.0))
          }
          None => {
            rc0 = 0.0
            rc1 = 0.0
          }
        }
      }
    }
    // is the sector collapsed to a line?
    if !(da1 > arc_epsilon) {
      ctx.move_to(x01, y01)
    } else if rc1 > arc_epsilon {
      // does the outer ring have rounded corners?
      let t0 = corner_tangents(x00, y00, x01, y01, r1, rc1, cw)
      let t1 = corner_tangents(x11, y11, x10, y10, r1, rc1, cw)
      ctx.move_to(t0.cx + t0.x01, t0.cy + t0.y01)
      if rc1 < rc {
        // the corners have merged
        ctx.arc(
          t0.cx,
          t0.cy,
          rc1,
          @util.v8_atan2(t0.y01, t0.x01),
          @util.v8_atan2(t1.y01, t1.x01),
          !cw,
        )
      } else {
        // otherwise: the two corners and the ring
        ctx.arc(
          t0.cx,
          t0.cy,
          rc1,
          @util.v8_atan2(t0.y01, t0.x01),
          @util.v8_atan2(t0.y11, t0.x11),
          !cw,
        )
        ctx.arc(
          0.0,
          0.0,
          r1,
          @util.v8_atan2(t0.cy + t0.y11, t0.cx + t0.x11),
          @util.v8_atan2(t1.cy + t1.y11, t1.cx + t1.x11),
          !cw,
        )
        ctx.arc(
          t1.cx,
          t1.cy,
          rc1,
          @util.v8_atan2(t1.y11, t1.x11),
          @util.v8_atan2(t1.y01, t1.x01),
          !cw,
        )
      }
    } else {
      // the outer ring is just a circular arc
      ctx.move_to(x01, y01)
      ctx.arc(0.0, 0.0, r1, a01, a11, !cw)
    }
    // is there no inner ring, and it is a circular sector?
    // or an annular sector collapsed by padding?
    if !(r0 > arc_epsilon) || !(da0 > arc_epsilon) {
      ctx.line_to(x10, y10)
    } else if rc0 > arc_epsilon {
      // does the inner ring have rounded corners?
      let t0 = corner_tangents(x10, y10, x11, y11, r0, -rc0, cw)
      let t1 = corner_tangents(x01, y01, x00, y00, r0, -rc0, cw)
      ctx.line_to(t0.cx + t0.x01, t0.cy + t0.y01)
      if rc0 < rc {
        ctx.arc(
          t0.cx,
          t0.cy,
          rc0,
          @util.v8_atan2(t0.y01, t0.x01),
          @util.v8_atan2(t1.y01, t1.x01),
          !cw,
        )
      } else {
        ctx.arc(
          t0.cx,
          t0.cy,
          rc0,
          @util.v8_atan2(t0.y01, t0.x01),
          @util.v8_atan2(t0.y11, t0.x11),
          !cw,
        )
        ctx.arc(
          0.0,
          0.0,
          r0,
          @util.v8_atan2(t0.cy + t0.y11, t0.cx + t0.x11),
          @util.v8_atan2(t1.cy + t1.y11, t1.cx + t1.x11),
          cw,
        )
        ctx.arc(
          t1.cx,
          t1.cy,
          rc0,
          @util.v8_atan2(t1.y11, t1.x11),
          @util.v8_atan2(t1.y01, t1.x01),
          !cw,
        )
      }
    } else {
      // the inner ring is just a circular arc
      ctx.arc(0.0, 0.0, r0, a10, a00, cw)
    }
  }
  ctx.close_path()
}

///|
/// `d` of an arc item, centred on the origin (3 decimals, as d3-shape).
pub fn arc_path(item : Item) -> String {
  let p = PathBuf::new(digits=3)
  draw_arc(Buf(p), item)
  p.to_string()
}