#import "@preview/zap:0.5.0": *
#import "@preview/cetz:0.4.2": (
  draw.anchor, draw.arc-through, draw.circle, draw.content, draw.line, draw.rect, draw.rotate, draw.get-ctx, draw.bezier
)

#let logic(name, node, text: $"&"$, invert: false, mirror: false, invert-inputs: (), angle: 0deg, inputs: 2, ..params) = {
  assert(inputs >= 1, message: "logic supports minimum one inputs")

  let style = (
    width: 0.7,
    min-height: 0.9,
    spacing: 0.4,
    padding: 0.25,
    fill: white,
    stroke: auto,
  )

  // Drawing function
  let draw(ctx, position, _) = {
    rotate(angle)

    let height = calc.max(style.min-height, (inputs - 1) * style.spacing + 2 * style.padding)
    let width = style.width * if mirror { -1 } else { 1 }

    interface((-width / 2, -height / 2), (width / 2, height / 2), io: false)

    rect((-width / 2, -height / 2), (rel: (width, height)), fill: style.fill, stroke: style.stroke)
    content((0, height / 2 - style.padding / 2), text, anchor: "north", angle: angle)

    let ball-radius = calc.min(height, width) * 0.1

    for input in range(1, inputs + 1) {
      let pad = (height - (inputs - 1) * style.spacing) / 2
      let y = height / 2 - pad - (input - 1) * style.spacing;

      if input in invert-inputs {
        circle((-width / 2 - ball-radius, y), radius: ball-radius, stroke: style.stroke, fill: style.fill)
        anchor("in" + str(input), (-width / 2, y))
      } else {
        anchor("in" + str(input), (-width / 2, y))
      }
    }

    if invert {
      circle((width / 2 + ball-radius, 0), radius: ball-radius, stroke: style.stroke, fill: style.fill)
      anchor("out", (width / 2, 0))
    } else {
      anchor("out", (width / 2, 0))
    }
  }

  // Component call
  component("logic", name, node, draw: draw, ..params)
}

#let lnot(name, node, ..params) = logic(name, node, ..params, text: $1$, invert: true)
#let land(name, node, ..params) = logic(name, node, ..params, text: $"&"$)
#let lnand(name, node, ..params) = logic(name, node, ..params, text: $"&"$, invert: true)
#let lor(name, node, ..params) = logic(name, node, ..params, text: $>=1$)
#let lnor(name, node, ..params) = logic(name, node, ..params, text: $>=1$, invert: true)
#let lxor(name, node, ..params) = logic(name, node, ..params, text: $=1$)
#let lxnor(name, node, ..params) = logic(name, node, ..params, text: $=1$, invert: true)