///|
/// A `Slice` is a slice of an `Array`.
///
/// A separate Slice type was needed because of this issue:
/// https://github.com/moonbitlang/core/issues/1063
///
/// This struct is based on MoonBit's "ArrayView" implementation here:
/// https://github.com/moonbitlang/core/blob/main/builtin/arrayview.mbt
/// which has the following copyright:
///
/// Copyright 2024 International Digital Economy Academy
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
struct Slice[T] {
  buf : Array[T]
  start : Int
  len : Int
} derive(Debug)

///|
/// length returns the length of the slice.
pub fn[T] Slice::length(self : Slice[T]) -> Int {
  self.len
}

///|
pub fn[T] Slice::op_get(self : Slice[T], index : Int) -> T {
  guard index >= 0 && index < self.len else {
    abort(
      "index out of bounds: the len is from 0 to \{self.len} but the index is \{index}",
    )
  }
  self.buf[self.start + index]
}

///|
pub fn[T] Slice::op_set(self : Slice[T], index : Int, value : T) -> Unit {
  guard index >= 0 && index < self.len else {
    abort(
      "index out of bounds: the len is from 0 to \{self.len} but the index is \{index}",
    )
  }
  self.buf[self.start + index] = value
}

///|
pub fn[T] Slice::swap(self : Slice[T], i : Int, j : Int) -> Unit {
  guard i >= 0 && i < self.len && j >= 0 && j < self.len else {
    abort(
      "index out of bounds: the len is from 0 to \{self.len} but the index is (\{i}, \{j})",
    )
  }
  let temp = self.buf[self.start + i]
  self.buf[self.start + i] = self.buf[self.start + j]
  self.buf[self.start + j] = temp
}

///|
pub fn[T] Slice::new(buf : Array[T], start? : Int = 0, end? : Int) -> Slice[T] {
  let len = buf.length()
  let end = match end {
    None => len
    Some(end) => end
  }
  guard start >= 0 && start <= end && end <= len else {
    abort("View start index out of bounds")
  }
  Slice::{ buf, start, len: end - start }
}

///|
pub fn[T] Slice::as_array_view(self : Slice[T]) -> ArrayView[T] {
  let end = self.len + self.start
  self.buf[self.start:end]
}

///|
pub fn[T] Slice::op_as_view(
  self : Slice[T],
  start? : Int = 0,
  end? : Int,
) -> Slice[T] {
  let len = self.buf.length()
  let end = match end {
    None => self.len
    Some(end) => end
  }
  guard self.start + start >= 0 && start <= end && self.start + end <= len else {
    abort("View start index out of bounds")
  }
  Slice::{ buf: self.buf, start: self.start + start, len: end - start }
}

///|
/// cap returns the total capacity of the slice regardless of the current length.
pub fn[T] Slice::cap(self : Slice[T]) -> Int {
  self.buf.length() - self.start
}

///|
/// append appends `s` to the end of the slice, reallocating the underlying
/// array if necessary, then returns the new slice.
pub fn[T] Slice::append(self : Slice[T], s : Slice[T]) -> Slice[T] {
  let remaining = self.cap() - self.length()
  if remaining >= s.length() {
    // plenty of room, reuse current internal Array.
    let idx = self.start + self.len
    for i in 0.. Slice[T] {
  self.append(Slice::new([e]))
}

///|
pub fn[A] Slice::iter(self : Slice[A]) -> Iter[A] {
  self.buf[self.start:self.start + self.len].iter()
}

///|
pub fn[A] Slice::iter2(self : Slice[A]) -> Iter2[Int, A] {
  self.buf[self.start:self.start + self.len].iter2()
}

///|
/// Fold out values from a Slice according to certain rules.
pub fn[A, B] Slice::fold(self : Slice[A], init~ : B, f : (B, A) -> B) -> B {
  for i = 0, acc = init; i < self.length(); {
    continue i + 1, f(acc, self[i])
  } nobreak {
    acc
  }
}

///|
test "fold" {
  let sum = [1, 2, 3, 4, 5][:].fold(init=0, fn(sum, elem) { sum + elem })
  inspect(sum, content="15")
}

///|
/// Fold out values from a Slice according to certain rules in reversed turn.
pub fn[A, B] Slice::rev_fold(self : Slice[A], init~ : B, f : (B, A) -> B) -> B {
  for i = self.length() - 1, acc = init; i >= 0; {
    continue i - 1, f(acc, self[i])
  } nobreak {
    acc
  }
}

///|
test "rev_fold" {
  let sum = [1, 2, 3, 4, 5][:].rev_fold(init=0, fn(sum, elem) { sum + elem })
  inspect(sum, content="15")
}

///|
/// Fold out values from a Slice according to certain rules with index.
pub fn[A, B] Slice::foldi(
  self : Slice[A],
  init~ : B,
  f : (Int, B, A) -> B,
) -> B {
  for i = 0, acc = init; i < self.length(); {
    continue i + 1, f(i, acc, self[i])
  } nobreak {
    acc
  }
}

///|
test "foldi" {
  let sum = [1, 2, 3, 4, 5][:].foldi(init=0, fn(index, sum, _elem) {
    sum + index
  })
  inspect(sum, content="10")
}

///|
/// Fold out values from a Slice according to certain rules in reversed turn with index.
pub fn[A, B] Slice::rev_foldi(
  self : Slice[A],
  init~ : B,
  f : (Int, B, A) -> B,
) -> B {
  let len = self.length()
  for i = len - 1, acc = init; i >= 0; {
    continue i - 1, f(len - i - 1, acc, self[i])
  } nobreak {
    acc
  }
}

///|
test "rev_foldi" {
  let sum = [1, 2, 3, 4, 5][:].rev_foldi(init=0, fn(index, sum, _elem) {
    sum + index
  })
  inspect(sum, content="10")
}

///|
pub impl[X : Show] Show for Slice[X] with fn output(self, logger) {
  logger.write_iter(self.iter())
}

// pub fn Slice::to_string[X : Show](self : Slice[X]) -> String {
//   Show::to_string(self)
// }

///|
pub fn[T] Slice::rev_inplace(self : Slice[T]) -> Unit {
  let mid_len = self.length() / 2
  for i = 0; i < mid_len; i = i + 1 {
    let j = self.length() - i - 1
    self.swap(i, j)
  }
}

///|
pub fn[T] Slice::each(self : Slice[T], f : (T) -> Unit) -> Unit {
  for i = 0; i < self.length(); i = i + 1 {
    f(self[i])
  }
}

///|
/// Compares two slices for equality.
pub impl[T : Eq] Eq for Slice[T] with fn equal(self, other) {
  guard self.length() == other.length() else { return false }
  for i = 0 {
    // CR: format issue
    if i >= self.length() {
      break true
    }
    if self[i] != other[i] {
      break false
    }
    continue i + 1
  }
}

///|
/// to_bytes returns slice as Bytes.
pub fn Slice::to_bytes(self : Slice[Byte]) -> Bytes {
  Bytes::from_iter(self.iter())
}