///|
struct Posix {
  root : BytesView
  anchor : BytesView
  parts : ArrayView[BytesView]
} derive(Eq, Hash)

///|
pub impl ToJson for Posix with to_json(self : Posix) -> Json {
  self.to_string().to_json()
}

///|
fn Posix::inspect(self : Posix) -> Json raise @utf8.Malformed {
  {
    "root": @utf8.decode(self.root),
    "anchor": @utf8.decode(self.anchor),
    "parts": self.parts.map(@utf8.decode(_)),
  }
}

///|
pub impl @json.FromJson for Posix with from_json(
  json : Json,
  path : @json.JsonPath,
) -> Posix {
  let string : String = @json.FromJson::from_json(json, path)
  Posix::parse(@utf8.encode(string))
}

///|
pub fn Posix::parts(self : Posix) -> @builtin.Iter[BytesView] {
  self.parts.iter()
}

///|
pub fn Posix::root(self : Posix) -> BytesView {
  self.root
}

///|
pub fn Posix::anchor(self : Posix) -> BytesView {
  self.anchor
}

///|
pub impl Compare for Posix with compare(self : Posix, other : Posix) -> Int {
  if self.anchor != other.anchor {
    return self.anchor.compare(other.anchor)
  }
  return self.parts.compare(other.parts)
}

///|
fn Posix::parse(path : BytesView) -> Posix {
  let root = @buffer.new()
  fn make_path(path : Array[BytesView]) -> Posix {
    let root = root.contents()
    return Posix::{ root, anchor: root, parts: path }
  }

  let parts : Array[BytesView] = []
  let part = @buffer.new()
  let path = match path {
    [.. "///", .. path] => {
      root.write_byte('/')
      parts.push("/")
      skip~: loop path {
        ['/', .. path] => continue skip~ path
        [char, .. path] => {
          part.write_byte(char)
          path
        }
        [] => return make_path(parts)
      }
    }
    [.. "//", .. path] => {
      root.write_bytes("//")
      parts.push("//")
      path
    }
    ['/', .. path] => {
      root.write_byte('/')
      parts.push("/")
      path
    }
    [.. path] => path
  }
  fn push_part(part : BytesView) {
    if part is ("" | ".") {
      return
    }
    parts.push(part)
  }

  path~: loop path {
    ['/', .. path] => {
      push_part(part.contents())
      skip~: loop path {
        ['/', .. path] => continue skip~ path
        [char, .. path] => {
          part.reset()
          part.write_byte(char)
          continue path~ path
        }
        [] => break path~ make_path(parts)
      }
    }
    [char, .. path] => {
      part.write_byte(char)
      continue path~ path
    }
    [] => {
      push_part(part.contents())
      return make_path(parts)
    }
  }
}

///|
test "posix_path/root" {
  @json.inspect(Posix::parse("/").inspect(), content={
    "root": "/",
    "anchor": "/",
    "parts": ["/"],
  })
  @json.inspect(Posix::parse("//").inspect(), content={
    "root": "//",
    "anchor": "//",
    "parts": ["//"],
  })
  @json.inspect(Posix::parse("///").inspect(), content={
    "root": "/",
    "anchor": "/",
    "parts": ["/"],
  })
  @json.inspect(Posix::parse("//usr//bin//").inspect(), content={
    "root": "//",
    "anchor": "//",
    "parts": ["//", "usr", "bin"],
  })
  @json.inspect(Posix::parse("////usr").inspect(), content={
    "root": "/",
    "anchor": "/",
    "parts": ["/", "usr"],
  })
  @json.inspect(Posix::parse("/usr/bin").inspect(), content={
    "root": "/",
    "anchor": "/",
    "parts": ["/", "usr", "bin"],
  })
}

///|
test "posix_path" {
  @json.inspect(Posix::parse("usr/bin").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "bin"],
  })
  @json.inspect(Posix::parse("usr/bin/").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "bin"],
  })
  @json.inspect(Posix::parse("usr//bin").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "bin"],
  })
  @json.inspect(Posix::parse("C:\\Windows").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["C:\\Windows"],
  })
}

///|
test "posix_path/empty" {
  @json.inspect(Posix::parse("").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": [],
  })
}

///|
test "posix_path/collapse_slashes" {
  @json.inspect(Posix::parse("usr/////bin").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "bin"],
  })
  @json.inspect(Posix::parse("usr//bin//").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "bin"],
  })
  @json.inspect(Posix::parse("usr///bin//").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "bin"],
  })
}

///|
test "posix_path/collapse_dots" {
  @json.inspect(Posix::parse("usr/./bin").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "bin"],
  })
  @json.inspect(Posix::parse("usr/../bin").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "..", "bin"],
  })
  @json.inspect(Posix::parse("usr/.././bin").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "..", "bin"],
  })
  @json.inspect(Posix::parse("usr/./../bin").inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "..", "bin"],
  })
}

///|
pub impl Show for Posix with output(self : Posix, logger : &Logger) -> Unit {
  if self.anchor == "" && self.parts is [] {
    logger.write_char('.')
    return
  }
  if self.root != "" {
    logger.write_string(@utf8.decode_lossy(self.root))
    let parts = self.parts[1:]
    for i, part in parts {
      logger.write_string(@utf8.decode_lossy(part))
      if i < parts.length() - 1 {
        logger.write_char('/')
      }
    }
  } else {
    for i, part in self.parts {
      logger.write_string(@utf8.decode_lossy(part))
      if i < self.parts.length() - 1 {
        logger.write_char('/')
      }
    }
  }
}

///|
pub fn Posix::to_bytes(self : Posix) -> Bytes {
  if self.anchor == "" && self.parts is [] {
    return "."
  }
  let buffer = @buffer.new()
  if self.root != "" {
    buffer.write_bytesview(self.root)
    let parts = self.parts[1:]
    for i, part in parts {
      buffer.write_bytesview(part)
      if i < parts.length() - 1 {
        buffer.write_byte('/')
      }
    }
  } else {
    for i, part in self.parts {
      buffer.write_bytesview(part)
      if i < self.parts.length() - 1 {
        buffer.write_byte('/')
      }
    }
  }
  buffer.contents()
}

///|
pub fn Posix::parent(self : Posix) -> Posix? {
  if self.parts.length() == 0 {
    return None
  }
  if self.root != "" && self.parts.length() == 1 {
    return None
  }
  Some(Posix::{ ..self, parts: self.parts[:self.parts.length() - 1] })
}

///|
test "BasicPath::parent" {
  let path = Posix::{ root: "", anchor: "", parts: ["usr", "bin"] }
  @json.inspect(path.parent(), content=["usr"])
  let path = Posix::{ root: "/", anchor: "/", parts: ["/", "usr", "bin"] }
  @json.inspect(path.parent(), content=["/usr"])
  let path = Posix::{ root: "", anchor: "", parts: [] }
  @json.inspect(path.parent() is None, content=true)
}

///|
pub impl ToWindows for Posix with to_windows(self : Posix) -> Windows {
  Windows::parse(self.to_bytes())
}

///|
pub fn[ToPosix : ToPosix] Posix::relative_to(
  self : Posix,
  other : ToPosix,
) -> Posix {
  let other = other.to_posix()
  let self_parts_length = self.parts.length()
  let other_parts_length = other.parts.length()
  let minimum_parts_length = @cmp.minimum(self_parts_length, other_parts_length)
  let start_index = if self.anchor != "" { 1 } else { 0 }
  let common_parts_length = for i in start_index.. Posix {
  let other = other.to_posix()
  if other.root != "" {
    return other
  }
  Posix::{ ..self, parts: [..self.parts, ..other.parts] }
}

///|
test "Path::join" {
  let a = Posix::{ root: "", anchor: "", parts: ["usr", "bin"] }
  let b = Posix::{ root: "", anchor: "", parts: ["local", "bin"] }
  let c = a.join(b)
  @json.inspect(c.inspect(), content={
    "root": "",
    "anchor": "",
    "parts": ["usr", "bin", "local", "bin"],
  })
  let a = Posix::{ root: "/", anchor: "/", parts: ["/", "usr", "bin"] }
  let b = Posix::{ root: "", anchor: "", parts: ["local", "bin"] }
  let c = a.join(b)
  @json.inspect(c.inspect(), content={
    "root": "/",
    "anchor": "/",
    "parts": ["/", "usr", "bin", "local", "bin"],
  })
  let a = Posix::{ root: "", anchor: "", parts: ["usr", "bin"] }
  let b = Posix::{ root: "/", anchor: "/", parts: ["/", "local", "bin"] }
  let c = a.join(b)
  @json.inspect(c.inspect(), content={
    "root": "/",
    "anchor": "/",
    "parts": ["/", "local", "bin"],
  })
}

///|
pub fn Posix::suffix(self : Posix) -> BytesView? {
  guard self.name() is Some(name) else { None }
  loop name {
    [] => None
    [.., '.'] as view => Some(view.data()[view.start_offset():])
    [.. rest, _] => continue rest
  }
}

///|
pub fn Posix::name(self : Posix) -> BytesView? {
  let parts_length = self.parts.length()
  if self.anchor != "" && self.parts.length() == 1 {
    return None
  }
  Some(self.parts[parts_length - 1])
}

///|
pub trait ToPosix {
  to_posix(Self) -> Posix
}

///|
pub impl ToPosix for String with to_posix(self : String) -> Posix {
  ToPosix::to_posix(self.view())
}

///|
pub impl ToPosix for @string.View with to_posix(self : @string.View) -> Posix {
  Posix::parse(@utf8.encode(self))
}

///|
pub impl ToPosix for Bytes with to_posix(self : Bytes) -> Posix {
  Posix::parse(self)
}

///|
pub impl ToPosix for BytesView with to_posix(self : BytesView) -> Posix {
  Posix::parse(self)
}

///|
pub impl ToPosix for Posix with to_posix(self : Posix) -> Posix {
  self
}

///|
pub impl[T : ToPosix] ToPosix for Array[T] with to_posix(self : Array[T]) -> Posix {
  ToPosix::to_posix(self[:])
}

///|
pub impl[T : ToPosix] ToPosix for ArrayView[T] with to_posix(
  self : ArrayView[T],
) -> Posix {
  let mut path = Posix::{ root: "", anchor: "", parts: [] }
  for item in self {
    path = path.join(item.to_posix())
  }
  path
}

///|
pub fn[ToPosix : ToPosix] posix(path : ToPosix) -> Posix {
  path.to_posix()
}