// Copyright 2021-2022 Ian Jackson and contributors to Hippotat
// SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception
// There is NO WARRANTY.

use crate::prelude::*;

#[derive(Default,Clone)]
pub struct PacketQueue<D> {
  queue: VecDeque<D>,
  content: usize,
}

impl<D> PacketQueue<D> where D: AsRef<[u8]> {
  pub fn push_back(&mut self, data: D) {
    self.content += data.as_ref().len();
    self.queue.push_back(data);
  }

  pub fn pop_front(&mut self) -> Option<D> {
    let data = self.queue.pop_front()?;
    self.content -= data.as_ref().len();
    Some(data)
  }

  pub fn content_count(&self) -> usize { self.queue.len() }
  pub fn content_len(&self) -> usize { self.content }
  pub fn total_len(&self) -> usize {
    self.content_count() + self.content_len()
  }

  pub fn is_empty(&self) -> bool { self.queue.is_empty() }
  pub fn peek_front(&self) -> Option<&D> { self.queue.front() }
}

#[derive(Educe,Clone)]
#[educe(Default)]
pub struct QueueBuf<E> {
  content: usize,
  eaten1: usize, // 0 <= eaten1 < queue.front()...len()
  queue: VecDeque<E>,
}

#[derive(Default,Debug,Clone)]
pub struct FrameQueueBuf {
  queue: QueueBuf<QueuedBytes>,
}

pub type QueuedBytes = Either<Box<[u8]>, &'static [u8]>;
use Either::Left as QueuedBytesOwned;
use Either::Right as QueuedBytesBorrowed;

impl<E> Debug for QueueBuf<E> where E: AsRef<[u8]> {
  #[throws(fmt::Error)]
  fn fmt(&self, f: &mut fmt::Formatter) {
    write!(f, "Queue{{content={},eaten1={},queue=[",
           self.content, self.eaten1)?;
    for q in &self.queue { write!(f, "{},", q.as_ref().len())?; }
    write!(f, "]}}")?;
  }
}

impl<E> QueueBuf<E> where E: AsRef<[u8]> {
  pub fn push<B: Into<E>>(&mut self, b: B) {
    self.push_(b.into());
  }
  fn push_(&mut self, b: E) {
    let l = b.as_ref().len();
    self.queue.push_back(b);
    self.content += l;
  }
  pub fn is_empty(&self) -> bool { self.content == 0 }
  pub fn len(&self) -> usize { self.content }
}

impl FrameQueueBuf {
  pub fn push_esc<B: Into<Box<[u8]>>>(&mut self, b: B) {
    self.push_esc_(b.into());
  }
  fn push_esc_(&mut self, b: Box<[u8]>) {
    self.queue.push_( QueuedBytesOwned(b));
    self.queue.push_(QueuedBytesBorrowed(SLIP_END_SLICE));
  }
  pub fn esc_push(&mut self, b: Box<[u8]>) {
    self.queue.push_(QueuedBytesBorrowed(SLIP_END_SLICE));
    self.queue.push_(QueuedBytesOwned(b));
  }
  pub fn push_raw(&mut self, b: Box<[u8]>) {
    self.queue.push_(QueuedBytesOwned(b));
  }
  pub fn is_empty(&self) -> bool { self.queue.is_empty() }
  pub fn len(&self) -> usize { self.queue.len() }
}

impl<E> hyper::body::Buf for QueueBuf<E> where E: AsRef<[u8]> {
  fn remaining(&self) -> usize { self.content }
  fn chunk(&self) -> &[u8] {
    let front = if let Some(f) = self.queue.front() { f } else { return &[] };
    &front.as_ref()[ self.eaten1.. ]
  }
  fn advance(&mut self, cnt: usize) {
    self.content -= cnt;
    self.eaten1 += cnt;
    loop {
      if self.eaten1 == 0 { break }
      let front = self.queue.front().unwrap();
      if self.eaten1 < front.as_ref().len() { break; }
      self.eaten1 -= front.as_ref().len();
      self.queue.pop_front().unwrap();
    }
  }
}

impl hyper::body::Buf for FrameQueueBuf {
  fn remaining(&self) -> usize { self.queue.remaining() }
  fn chunk(&self) -> &[u8] { self.queue.chunk() }
  fn advance(&mut self, cnt: usize) { self.queue.advance(cnt) }
}

pin_project!{
  pub struct BufBody<B:Buf> {
    body: Option<B>,
  }
}
impl<B:Buf> BufBody<B> {
  pub fn new(body: B) -> Self { Self { body: Some(body ) } }
}
impl BufBody<FrameQueueBuf> {
  pub fn display<S:Display>(s: S) -> Self {
    let s = s.to_string().into_bytes();
    let mut buf: FrameQueueBuf = default();
    buf.push_raw(s.into());
    Self::new(buf)
  }
}

impl<B:Buf> hyper::body::Body for BufBody<B> {
  type Error = Void;
  type Data = B;
  fn poll_frame(self: Pin<&mut Self>, _: &mut std::task::Context<'_>)
                -> Poll<Option<Result<hyper::body::Frame<B>, Void>>> {
    Poll::Ready(Ok(self.project().body.take()
                   .map(hyper::body::Frame::data)
    ).transpose())
  }
}
