// L-System KCL
// Zoo Corporation ⓒ 2024

// Comparators

fn cond(@bools) {
  return fn(a, b) {
    x = min([max([-1, a-b]), 1]) + 1
    return bools[x]
  }
}

fn Not(@b) { return if b { false } else { true } }
fn And(a, b) { return if a { if b { true } else { false } } else { false }}
fn Or(a, b) { return if a { true } else { if b { true } else { false }}}

Eq = cond([false, true, false])
Lt = cond([true, false, false])
Gt = cond([false, false, true])

fn Lte(a, b) { return Not(Gt(a = a, b = b)) }
fn Gte(a, b) { return Not(Lt(a = a, b = b)) }

// L-system
// Note: it was most concise to encode productions directly in axioms.
// Change them as you need.

fn setSketch(@state, q) {
  return {
    depthMax = state.depthMax,
    depth = state.depth + 1,
    currentLength = state.currentLength,
    factor = state.factor,
    currentAngle = state.currentAngle,
    angle = state.angle,
    q
  }
}

fn setDepth(@state, q) {
  return {
    depthMax = state.depthMax,
    depth = q,
    currentLength = state.currentLength,
    factor = state.factor,
    currentAngle = state.currentAngle,
    angle = state.angle,
    q = state.q
  }
}

fn setAngle(@state, q) {
  return {
    depthMax = state.depthMax,
    depth = state.depth,
    currentLength = state.currentLength,
    factor = state.factor,
    currentAngle = q,
    angle = state.angle,
    q = state.q
  }
}

fn setLength(@state, q) {
  return {
    depthMax = state.depthMax,
    depth = state.depth,
    currentLength = q,
    factor = state.factor,
    currentAngle = state.currentAngle,
    angle = state.angle,
    q = state.q
  }
}

fn Gt2(@state) { return setLength(state, q = state.currentLength * state.factor) }
fn Lt2(@state) { return setLength(state, q = state.currentLength / state.factor) }
fn Add(@state) { return setAngle(state, q = rem(state.currentAngle - state.angle, divisor = 360)) }
fn Sub(@state) { return setAngle(state, q = rem(state.currentAngle + state.angle, divisor = 360)) }

// Only necessary to get around recursion limitations...
fn F(@state, F) {
  return if Lt(a = state.depth, b = state.depthMax) {
    stateNext = state |> setDepth(%, q = state.depth + 1)

    // Produce
    // Note:if you need [ and ], just save state to a variable.
    stateNext
      |> F(%, F = F) |> Sub(%) |> F(%, F = F)
      |> Add(%) |> Add(%)
      |> F(%, F = F) |> Sub(%) |> F(%, F = F)
      |> setDepth(%, q = stateNext.depth - 1)

  } else {
    // Pass onto the next instruction
    state |> setSketch(%, q = angledLine(state.q, angle = state.currentAngle, length = state.currentLength))
  }
}

fn LSystem(@args, axioms) {
    myThing = startSketchOn(XY)
    |> startProfile(at = [0, 0])
  return axioms({
    depthMax = args.iterations,
    depth = 0,
    currentLength = 1.0,
    factor = args.factor,
    currentAngle = 0,
    angle = args.angle,
    q = myThing,
  })
}

LSystem({
  iterations = 1,
  factor = 1.36,
  angle = 60,
}, axioms = fn(@q) {
  result = q |> F(%, F = F) |> Add(%) |> Add(%) |> F(%, F = F) |> Add(%) |> Add(%) |> F(%, F = F)
  return result.q
})

