// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.

use std::io;
use std::pin::Pin;
use std::sync::Arc;
use std::task::Context;
use std::task::Poll;
use std::task::ready;

use bytes::Bytes;
use futures::Stream;
use futures::StreamExt;

use crate::raw::*;
use crate::*;

/// FuturesBytesStream is the adapter of [`Stream`] generated by [`Reader::into_bytes_stream`].
///
/// Users can use this adapter in cases where they need to use [`Stream`] trait. FuturesBytesStream
/// reuses the same concurrent adand chunk settings from [`Reader`].
///ad
/// FuturesStream also implements [`Unpin`], [`Send`] and [`Sync`].
pub struct FuturesBytesStream {
    stream: BufferStream,
    buf: Buffer,
}

/// Safety: FuturesBytesStream only exposes `&mut self` to the outside world,
unsafe impl Sync for FuturesBytesStream {}

impl FuturesBytesStream {
    /// NOTE: don't allow users to create FuturesStream directly.
    pub(crate) async fn new(ctx: Arc<ReadContext>, range: impl Into<BytesRange>) -> Result<Self> {
        let stream = BufferStream::create(ctx, range).await?;

        Ok(FuturesBytesStream {
            stream,
            buf: Buffer::new(),
        })
    }

    /// Get metadata for this stream.
    ///
    /// Calling this method opens the underlying read request if needed.
    /// Returns [`ErrorKind::Unsupported`] if the underlying service doesn't
    /// return metadata while opening the read operation.
    pub async fn metadata(&mut self) -> Result<Metadata> {
        self.stream.metadata().await
    }
}

impl Stream for FuturesBytesStream {
    type Item = io::Result<Bytes>;

    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        let this = self.get_mut();

        loop {
            // Consume current buffer
            if let Some(bs) = Iterator::next(&mut this.buf) {
                return Poll::Ready(Some(Ok(bs)));
            }

            this.buf = match ready!(this.stream.poll_next_unpin(cx)) {
                Some(Ok(buf)) => buf,
                Some(Err(err)) => return Poll::Ready(Some(Err(format_std_io_error(err)))),
                None => return Poll::Ready(None),
            };
        }
    }
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use bytes::Bytes;
    use futures::TryStreamExt;
    use pretty_assertions::assert_eq;

    use super::*;

    fn new_read_context(
        ctx: OperationContext,
        srv: Servicer,
        path: &str,
        options: crate::raw::OpReader,
    ) -> crate::Result<ReadContext> {
        let args = crate::raw::OpRead::new();
        let reader = srv.read(&ctx, path, args.clone())?;
        Ok(ReadContext::new(
            ctx,
            srv,
            path.to_string(),
            args,
            options,
            reader,
        ))
    }

    #[tokio::test]
    async fn test_trait() -> Result<()> {
        let op = Operator::via_iter(services::MEMORY_SCHEME, [])?;
        let ctx = op.context().clone();
        let srv = op.service().clone();
        let ctx = Arc::new(new_read_context(ctx, srv, "test", OpReader::new())?);
        let v = FuturesBytesStream::new(ctx, 4..8).await?;

        let _: Box<dyn Unpin + MaybeSend + Sync + 'static> = Box::new(v);

        Ok(())
    }

    #[tokio::test]
    async fn test_futures_bytes_stream() -> Result<()> {
        let op = Operator::via_iter(services::MEMORY_SCHEME, [])?;
        op.write(
            "test",
            Buffer::from(vec![Bytes::from("Hello"), Bytes::from("World")]),
        )
        .await?;

        let ctx = op.context().clone();
        let srv = op.service().clone();
        let ctx = Arc::new(new_read_context(ctx, srv, "test", OpReader::new())?);

        let s = FuturesBytesStream::new(ctx, 4..8).await?;
        let bufs: Vec<Bytes> = s.try_collect().await.unwrap();
        assert_eq!(&bufs[0], "o".as_bytes());
        assert_eq!(&bufs[1], "Wor".as_bytes());

        Ok(())
    }

    #[tokio::test]
    async fn test_futures_bytes_stream_with_concurrent() -> Result<()> {
        let op = Operator::via_iter(services::MEMORY_SCHEME, [])?;
        op.write(
            "test",
            Buffer::from(vec![Bytes::from("Hello"), Bytes::from("World")]),
        )
        .await?;

        let ctx = op.context().clone();
        let srv = op.service().clone();
        let ctx = Arc::new(new_read_context(
            ctx,
            srv,
            "test",
            OpReader::new().with_concurrent(3).with_chunk(1),
        )?);

        let s = FuturesBytesStream::new(ctx, 4..8).await?;
        let bufs: Vec<Bytes> = s.try_collect().await.unwrap();
        assert_eq!(&bufs[0], "o".as_bytes());
        assert_eq!(&bufs[1], "W".as_bytes());
        assert_eq!(&bufs[2], "o".as_bytes());
        assert_eq!(&bufs[3], "r".as_bytes());

        Ok(())
    }
}
