///|
/// A source position reported by parser diagnostics.
pub(all) struct Position {
  offset : Int
  line : Int
  column : Int
} derive(Eq, Debug)

///|
/// Operations shared by parser input cursors. `cursor` must increase whenever
/// an input element is consumed.
pub(open) trait Cursor {
  fn cursor(Self) -> Int
  fn position(Self) -> Position
  fn is_at_eof(Self) -> Bool
  fn same_cursor(Self, other : Self) -> Bool = _
}

///|
/// Immutable character input cursor.
/// Offsets are UTF-16 code unit offsets, matching MoonBit string indexing.
pub(all) struct Input {
  source : String
  offset : Int
  line : Int
  column : Int
} derive(Eq, Debug)

///|
pub fn Input::new(source : String) -> Input {
  { source, offset: 0, line: 1, column: 1 }
}

///|
pub fn Input::is_eof(self : Input) -> Bool {
  self.offset >= self.source.length()
}

///|
pub fn Input::peek(self : Input) -> Char? {
  self.source.get_char(self.offset)
}

///|
pub fn Input::next(self : Input) -> (Char, Input)? {
  match self.peek() {
    Some(ch) => Some((ch, self.advance(ch)))
    None => None
  }
}

///|
pub fn Input::advance(self : Input, ch : Char) -> Input {
  if ch == '\n' {
    Input::{
      source: self.source,
      offset: self.offset + ch.utf16_len(),
      line: self.line + 1,
      column: 1,
    }
  } else {
    Input::{
      source: self.source,
      offset: self.offset + ch.utf16_len(),
      line: self.line,
      column: self.column + 1,
    }
  }
}

///|
pub fn Input::advance_string(self : Input, text : String) -> Input {
  let mut input = self
  for ch in text.iter() {
    input = input.advance(ch)
  }
  input
}

///|
pub fn Input::remaining(self : Input) -> StringView {
  self.source.view(start_offset=self.offset)
}

///|
pub impl Cursor for Input with fn cursor(self) {
  self.offset
}

///|
pub impl Cursor for Input with fn position(self) {
  { offset: self.offset, line: self.line, column: self.column }
}

///|
pub impl Cursor for Input with fn is_at_eof(self) {
  self.is_eof()
}

///|
/// Checks whether the cursor has the same position as `other`.
///
/// Parameters:
///
/// * `self` : The current cursor.
/// * `other` : Another cursor to compare against.
///
/// Used internally by `many` and `many_until` to detect parsers
/// that accepted empty input.
///
/// Returns `true` if both cursors are at the same position.
impl Cursor with fn same_cursor(self, other) {
  self.cursor() == other.cursor()
}