use std::io;
use std::marker::PhantomData;

use codeq::error_context_ext::ErrorContextExt;
use codeq::Decode;

use crate::offset_reader::OffsetReader;
use crate::types::Segment;
use crate::ChunkId;
use crate::Types;
use crate::WALRecord;

pub(crate) struct RecordIterator<R, T> {
    r: OffsetReader<R>,
    total_size: u64,
    chunk_id: ChunkId,
    error: Option<io::Error>,
    _p: PhantomData<T>,
}

impl<R, T> RecordIterator<R, T>
where
    R: io::Read,
    T: Types,
{
    pub(crate) fn new(r: R, size: u64, chunk_id: ChunkId) -> Self {
        Self {
            r: OffsetReader::new(r),
            total_size: size,
            chunk_id,
            error: None,
            _p: Default::default(),
        }
    }
}

impl<R, T> Iterator for RecordIterator<R, T>
where
    R: io::Read,
    T: Types,
{
    type Item = Result<(Segment, WALRecord<T>), io::Error>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.error.is_some() {
            return None;
        }

        let start = self.r.offset();
        if start as u64 == self.total_size {
            return None;
        }

        let r = WALRecord::<T>::decode(&mut self.r);

        let res = r
            .map(|r| {
                (
                    Segment::new(
                        start as u64,
                        (self.r.offset() - start) as u64,
                    ),
                    r,
                )
            })
            .context(|| format!("decode Record at offset {}", start))
            .context(|| format!("iterate {}", self.chunk_id));

        if let Err(ref e) = res {
            self.error = Some(io::Error::new(e.kind(), e.to_string()));
        }

        Some(res)
    }
}
