///|
/// Turtle state used to turn production rules into botanical and geometric paths.
pub(all) struct TurtleState {
  mut position : Vec2
  mut heading : Double
  step : Double
  turn : Double
} derive(Eq, Debug, ToJson)

///|
pub fn TurtleState::new(
  x : Double,
  y : Double,
  step : Double,
  turn : Double,
) -> TurtleState {
  { position: Vec2::new(x, y), heading: 0.0, step, turn }
}

///|
pub(all) struct LSystem {
  axiom : String
  rules : Map[String, String]
} derive(Debug)

///|
pub fn LSystem::new(axiom : String) -> LSystem {
  { axiom, rules: {} }
}

///|
pub fn LSystem::rule(
  self : LSystem,
  symbol : String,
  replacement : String,
) -> LSystem {
  self.rules[symbol] = replacement
  self
}

///|
pub fn LSystem::expand(
  self : LSystem,
  iterations : Int,
  max_symbols? : Int = 200000,
) -> String {
  let mut current = self.axiom
  let limit = max_symbols
  for _ in 0.. next.write_string(value)
        None => next.write_string(key)
      }
      if next.to_string().length() >= limit {
        break
      }
    }
    current = next.to_string()
    if current.length() >= limit {
      break
    }
  }
  if current.length() > limit {
    current[:limit].to_owned()
  } else {
    current
  }
}

///|
/// Interprets F/f as forward, +/- as turns, and [] as a branch stack.
pub fn turtle_render(program : String, state : TurtleState) -> Path {
  let path = Path::new()
  let stack = []
  let mut current = state
  for symbol in program.to_array() {
    match symbol {
      'F' | 'f' => {
        let next = current.position.add({
          x: @math.cos(current.heading) * current.step,
          y: @math.sin(current.heading) * current.step,
        })
        let _ = path.line_to(next.x, next.y)
        current.position = next
      }
      '+' => current.heading += current.turn
      '-' => current.heading -= current.turn
      '[' => stack.push(current)
      ']' =>
        match stack.pop() {
          Some(saved) => {
            current = saved
            let _ = path.move_to(saved.position.x, saved.position.y)
          }
          None => ()
        }
      _ => ()
    }
  }
  path
}

///|
pub fn fern_lsystem(iterations : Int) -> String {
  LSystem::new("X")
  .rule("X", "F+[[X]-X]-F[-FX]+X")
  .rule("F", "FF")
  .expand(iterations)
}

///|
pub fn koch_lsystem(iterations : Int) -> String {
  LSystem::new("F").rule("F", "F+F-F-F+F").expand(iterations)
}

///|
pub fn dragon_lsystem(iterations : Int) -> String {
  LSystem::new("FX").rule("X", "X+YF+").rule("Y", "-FX-Y").expand(iterations)
}