///|
pub(open) trait WriteToStringBuilder: Show {
  write_to(Self, StringBuilder) -> Unit
}

///|
pub(open) trait WriteToBuffer: Show {
  write_to(Self, @buffer.T) -> Unit
}

///|
pub fn[E : WriteToStringBuilder] write_to_stringbuilder(
  acc : StringBuilder,
  e : E
) -> Unit {
  e.write_to(acc)
}

///|
pub fn[E : WriteToBuffer] write_to_buffer(acc : @buffer.T, e : E) -> Unit {
  e.write_to(acc)
}

///|
pub impl WriteToStringBuilder for Char with write_to(self, builder) {
  builder.write_char(self)
}

///|
pub impl WriteToStringBuilder for StringBuilder with write_to(self, builder) {
  builder.write_string(self.to_string())
}

///|
pub impl WriteToStringBuilder for String with write_to(self, builder) {
  builder.write_string(self)
}

///|
pub impl WriteToBuffer for Char with write_to(self, builder) {
  builder.write_char(self)
}

///|
pub impl WriteToBuffer for String with write_to(self, builder) {
  builder.write_string(self)
}

///|
pub impl WriteToBuffer for Byte with write_to(self, builder) {
  builder.write_byte(self)
}

///|
pub impl WriteToBuffer for Bytes with write_to(self, builder) {
  builder.write_bytes(self)
}

///|
pub impl[T : WriteToStringBuilder] WriteToStringBuilder for T? with write_to(
  self,
  builder
) {
  match self {
    Some(a) => a |> T::write_to(builder)
    None => ()
  }
}

///|
pub impl[T : WriteToStringBuilder] JoinToString for Array[T] with join_all(self) {
  let builder = StringBuilder::new()
  for a in self {
    a.write_to(builder)
  }
  builder.to_string()
}

///|
pub impl[T : WriteToStringBuilder] JoinToString for Iter[T] with join_all(self) {
  let builder = StringBuilder::new()
  for a in self {
    a.write_to(builder)
  }
  builder.to_string()
}

///|
pub impl[T : WriteToStringBuilder] JoinToString for FixedArray[T] with join_all(
  self
) {
  let builder = StringBuilder::new()
  for a in self.iter() {
    a.write_to(builder)
  }
  builder.to_string()
}

///|
pub impl[T : WriteToStringBuilder] JoinToString for Many[T] with join_all(self) {
  self.inner() |> JoinToString::join_all()
}

///|
pub fn[T : JoinToString] join_all(all : T) -> String {
  all.join_all()
}

///|
pub fn join_many(all : Many[&WriteToStringBuilder]) -> String {
  all.join_all()
}

///|
pub fn join_array(all : Array[&WriteToStringBuilder]) -> String {
  all |> JoinToString::join_all()
}

///|
pub fn join_fixed_array(all : FixedArray[&WriteToStringBuilder]) -> String {
  all |> JoinToString::join_all()
}

///|
pub fn join_iter(all : Iter[&WriteToStringBuilder]) -> String {
  all |> JoinToString::join_all()
}

///|
pub fn[A : WriteToStringBuilder, B : WriteToStringBuilder] join(
  a : A,
  b : B
) -> String {
  let builder = StringBuilder::new()
  a.write_to(builder)
  b.write_to(builder)
  // println(builder.to_string())
  builder.to_string()
}

///|
pub fn[A : WriteToStringBuilder, B : WriteToStringBuilder, C : WriteToStringBuilder] join3(
  a : A,
  b : B,
  c : C
) -> String {
  let builder = StringBuilder::new()
  a.write_to(builder)
  b.write_to(builder)
  c.write_to(builder)
  // println(builder.to_string())
  builder.to_string()
}

///|
pub impl Semigroup for &WriteToStringBuilder with op_add(a, b) {
  let builder = StringBuilder::new()
  a.write_to(builder)
  b.write_to(builder)
  builder.to_string()
}

///|
pub impl Semigroup for &WriteToStringBuilder with concat_many(a, b) {
  let builder = StringBuilder::new()
  a.write_to(builder)
  for a in b {
    a.write_to(builder)
  }
  builder.to_string()
}

///|
pub fn Lazy::string_builder() -> StringBuilder {
  StringBuilder::new()
}