/* This was originally my head.typ file from 3rd semester of calculus.
 * But I stripped all the math formatting stuff.
 *  */

#import calc

#let norm-vec2(A) = calc.sqrt(A.at(0) * A.at(0) + A.at(1) * A.at(1))

#let rotate-vec2(V, angle) = (
  calc.cos(angle) * V.at(0) + calc.sin(angle) * V.at(1),
  -calc.sin(angle) * V.at(0) + calc.cos(angle) * V.at(1)
)

#let rotate-vec2-90deg(V) = (V.at(1), -V.at(0))

#let mul-vec2-scal(A, b) = (A.at(0) * b, A.at(1) * b)

#let add-vec2(A, B) = (A.at(0) + B.at(0), A.at(1) + B.at(1))

#let sub-vec2(A, B) = (A.at(0) - B.at(0), A.at(1) - B.at(1))

#let make-length-vec2(fv) = mul-vec2-scal(fv, 1pt)

#let draw-something(cnt) = place(top + left, dx: 0pt, dy: 0pt, cnt)

#let draw-line(start, end, stroke: black) = draw-something(
  line(start: make-length-vec2(start), end: make-length-vec2(end), stroke: stroke))

#let draw-arrow-head(start, end, arm-sz: 4.5, stroke: black) = {
  let d = sub-vec2(start, end)
  let L = norm-vec2(d)
  let arrow_angle = calc.pi * 3/16
  let a1 = rotate-vec2(d, -arrow_angle)
  let a2 = rotate-vec2(d, arrow_angle)
  if (L > arm-sz){
    a1 = mul-vec2-scal(a1, arm-sz/L)
    a2 = mul-vec2-scal(a2, arm-sz/L)
  }

  let tg_end = make-length-vec2(end)
  let tg_a1 = make-length-vec2(a1)
  let tg_a2 = make-length-vec2(a2)

  show line: draw-something
  line(start: add-vec2(tg_end, tg_a1), end: tg_end, stroke: stroke)
  line(start: add-vec2(tg_end, tg_a2), end: tg_end, stroke: stroke)
}

#let draw-arrow(start, end, stroke: black) = {
  let tg_start = make-length-vec2(start)
  let tg_end = make-length-vec2(end)

  show line: draw-something
  line(start: tg_start, end:tg_end, stroke: stroke)
  draw-arrow-head(start, end, stroke: stroke)
}

#let draw-content(pos, cnt) = place(top + left,
dx: pos.at(0) * 1pt, dy: pos.at(1) * 1pt, cnt)

#let linp(A1, B1, A2, B2, x) = (x - A1) * (B2 - A2) / (B1 - A1) + A2

#let CSCpos2pac(csc, pan_dim, pos) = (
  linp(csc.X_at_left, csc.X_at_right, 0, pan_dim.at(0), pos.at(0)),
  linp(csc.Y_at_bottom, csc.Y_at_top, pan_dim.at(1), 0, pos.at(1))
)

#let draw-coordinate-system(csc, pan_dim,
Ox_marks: (), Oy_marks: (), x_name: $x$, y_name: $y$) = {
  let pac_y_of_Ox = linp(csc.Y_at_top, csc.Y_at_bottom, 0, pan_dim.at(1), 0)
  let Ox_enters_pac = (0, pac_y_of_Ox)
  let Ox_leaves_pac = (pan_dim.at(0), pac_y_of_Ox)
  let pac_x_of_Oy = linp(csc.X_at_left, csc.X_at_right, 0, pan_dim.at(0), 0)
  let Oy_enters_pac = (pac_x_of_Oy, pan_dim.at(1))
  let Oy_leaves_pac = (pac_x_of_Oy, 0)
  draw-arrow(Ox_enters_pac, Ox_leaves_pac)
  draw-arrow(Oy_enters_pac, Oy_leaves_pac)

  let funny_offset_Ox = (-5, 2)
  let funny_offset_Oy = (3, -6)

  for (val, cnt) in Ox_marks {
    let pan_pac = CSCpos2pac(csc, pan_dim, (val, 0))
    draw-line(add-vec2(pan_pac, (0, 2)), add-vec2(pan_pac, (0, -2)))
    draw-content(add-vec2(pan_pac, funny_offset_Ox), cnt)
  }
  for (val, cnt) in Oy_marks {
    let pan_pac = CSCpos2pac(csc, pan_dim, (0, val))
    draw-line(add-vec2(pan_pac, (2, 0)), add-vec2(pan_pac, (-2, 0)))
    draw-content(add-vec2(pan_pac, funny_offset_Oy), cnt)
  }
  draw-content(add-vec2(Ox_leaves_pac, funny_offset_Ox), x_name)
  draw-content(add-vec2(Oy_leaves_pac, funny_offset_Oy), y_name)
}

#let fix-csc(csc, pan_dim) = {
  let X_at_left = csc.at("X_at_left", default: none)
  let X_at_right = csc.at("X_at_right", default: none)
  let Y_at_bottom = csc.at("Y_at_bottom", default: none)
  let Y_at_top = csc.at("Y_at_top", default: none)

  let goodX = X_at_left != none and X_at_right != none
  let goodY = Y_at_bottom != none and Y_at_top != none
  if ((not goodX) and (not goodY)){
    panic("This is wrong")
  }
  if (goodX and goodY) {

  } else if (goodX){
    let yd = (X_at_right - X_at_left) * pan_dim.at(1) / pan_dim.at(0)
    if (Y_at_bottom != none){
      csc.Y_at_top = Y_at_bottom + yd
    } else if (Y_at_top != none){
      csc.Y_at_bottom = Y_at_top - yd
    } else {
      csc.Y_at_bottom = -yd/2
      csc.Y_at_top = yd/2
    }
  } else if (goodY){
    let xd = (Y_at_top - Y_at_bottom) * pan_dim.at(0) / pan_dim.at(1)
    if (X_at_left != none){
      csc.X_at_right = X_at_left + xd
    } else if (A_at_right != none){
      csc.X_at_left = X_at_right - xd
    } else {
      csc.X_at_left = -xd/2
      csc.X_at_right = xd/2
    }
  }
  return csc
}

// csc is linear coordinate system configurration for display on panel
#let plot-function-nocheck(csc, pan_dim, func, stroke: black+1pt) = {
  let points = range(0, calc.floor(pan_dim.at(0)) + 1).map((x_pac) => {
    let x = linp(0, pan_dim.at(0), csc.X_at_left, csc.X_at_right, x_pac)
    let y = func(x)
    let y_pac = linp(csc.Y_at_bottom, csc.Y_at_top, pan_dim.at(1), 0, y)
    (x_pac * 1pt, y_pac * 1pt)
  })
  draw-something(path(stroke: stroke, ..points))
}

#let plot-path-nocheck(csc, pan_dim, ..points, stroke: black) = {
  draw-something(path(stroke: stroke,
  ..(points).pos().map(point =>
  make-length-vec2(CSCpos2pac(csc, pan_dim, point)))))
}

#let plot-closed-figure-nocheck(csc, pan_dim, ..points, stroke: black, fill: none) = {
  draw-something(path(stroke: stroke, fill: fill, closed: true,
  ..(points).pos().map(point =>
  make-length-vec2(CSCpos2pac(csc, pan_dim, point)))))
}

#let ensureTypeRelative(x) = {
  if type(x) == relative {
    return x
  } else if type(x) == ratio {
    return x + 0pt
  } else if type(x) == length {
    return x + 0%
  }
  panic("Not supported")
}

#let drawing-rectangle(ab_width, ab_height, drawing_function, stroke: none) = context {
  let rl_width = ensureTypeRelative(ab_width)
  let rl_height = ensureTypeRelative(ab_height)
  layout(size => {
    let dim = (
      (rl_width.ratio * size.width + rl_width.length.to-absolute()).pt(),
      (rl_height.ratio * size.height + rl_height.length.to-absolute()).pt()
    )

    rect(width: rl_width, height: rl_height, inset: 0pt,
    stroke: stroke, drawing_function(dim))
  })
}
