// Copyright 2025 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.

///|
/// A buffered writer with a fixed size buffer that wraps around a normal writer.
/// Data will be buffered,
/// instead of being comitted to the underlying writer immediately,
/// unless the buffer is full or explicit flush is requested.
/// Buffering can can improve performance for network connections etc.
/// where small writes are expensive.
///
/// Always remember to call `flush` before writing directly to the underlying writer.
/// Otherwise some data may remain uncommitted.
struct BufferedWriter[W] {
  writer : W
  buf : FixedArray[Byte]
  mut len : Int
}

///|
/// Create a buffered writer with fixed buffer size `size`.
pub fn[W] BufferedWriter::new(
  writer : W,
  size? : Int = 1024,
) -> BufferedWriter[W] {
  { writer, buf: FixedArray::make(size, 0), len: 0 }
}

///|
/// Return the internal buffer size of a buffered writer.
pub fn[W] BufferedWriter::capacity(self : BufferedWriter[W]) -> Int {
  self.buf.length()
}

///|
/// Flush all buffered data and commit them to the underlying writer.
pub async fn[W : Writer] BufferedWriter::flush(
  self : BufferedWriter[W],
) -> Unit {
  if self.len > 0 {
    self.writer.write(self.buf.unsafe_reinterpret_as_bytes()[:self.len])
    self.len = 0
  }
}

///|
pub impl[W : Writer] Writer for BufferedWriter[W] with fn write_once(
  self,
  buf,
  offset~,
  len~,
) {
  if self.len >= self.buf.length() {
    self.flush()
  }
  let len = @cmp.minimum(len, self.buf.length() - self.len)
  self.buf.blit_from_bytes(self.len, buf, offset, len)
  self.len += len
  len
}

///|
pub impl[W : Writer] Writer for BufferedWriter[W] with fn write_reader(
  self,
  reader,
) {
  for ;; {
    if self.len >= self.buf.length() {
      self.flush()
    }
    let n = reader.read(
      self.buf,
      offset=self.len,
      max_len=self.buf.length() - self.len,
    )
    if n == 0 {
      break
    }
    self.len += n
  }
}