use crate::error::{SKError, SKResult};
use obikseq::kmer::CanonicalKmer;
use obikseq::superkmer::SuperKmer;
use std::fs::File;
use std::io::{self, Read};
use std::path::{Path, PathBuf};

/// Binary reader for SuperKmers, with transparent decompression via niffler.
///
/// Access is strictly sequential. Call [`iter`](SKFileReader::iter) to get an
/// [`Iterator`] over the SuperKmers, or [`iter_canonical_kmers`](SKFileReader::iter_canonical_kmers)
/// to iterate directly over the canonical kmers they contain.
pub struct SKFileReader {
    path: PathBuf,
    reader: Box<dyn std::io::Read + Send>,
}

impl SKFileReader {
    /// Open a file for reading. Format is auto-detected from magic bytes.
    pub fn open<P: AsRef<Path>>(path: P) -> SKResult<Self> {
        let path = path.as_ref().to_owned();
        let (reader, _fmt) =
            niffler::send::get_reader(Box::new(File::open(&path)?))?;
        Ok(Self { path, reader })
    }

    /// Read the next SuperKmer, or `None` at EOF.
    pub fn read(&mut self) -> SKResult<Option<SuperKmer>> {
        Ok(read_superkmer(&mut self.reader)?)
    }

    /// Read up to `n` SuperKmers.
    pub fn read_batch(&mut self, n: usize) -> SKResult<Vec<SuperKmer>> {
        let mut batch = Vec::with_capacity(n);
        for _ in 0..n {
            match self.read()? {
                Some(sk) => batch.push(sk),
                None => break,
            }
        }
        Ok(batch)
    }

    /// Physical path of this file.
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Return an iterator over the SuperKmers in this file.
    pub fn iter(&mut self) -> SKFileIter<'_> {
        SKFileIter {
            reader: self,
            error: None,
        }
    }

    /// Return an iterator over all canonical kmers across all SuperKmers in this file.
    ///
    /// Kmers are yielded in file order: all kmers of the first SuperKmer, then
    /// all kmers of the second, and so on. No deduplication is performed.
    pub fn iter_canonical_kmers(&mut self) -> impl Iterator<Item = CanonicalKmer> + '_ {
        self.iter().flat_map(|sk| sk.into_canonical_kmers())
    }
}

// ── SKFileIter ────────────────────────────────────────────────────────────────

/// Iterator over [`SuperKmer`]s in a file.
///
/// Errors during iteration are stored and accessible via [`take_error`](SKFileIter::take_error).
pub struct SKFileIter<'a> {
    reader: &'a mut SKFileReader,
    error: Option<SKError>,
}

impl<'a> SKFileIter<'a> {

      /// Read the next SuperKmer, or `None` at EOF.
      pub fn read(&mut self) -> SKResult<Option<SuperKmer>> {
          match self.reader.read()? {
              Some(sk) => Ok(Some(sk)),
              None => Ok(None),
           }
       }
    /// Returns the first I/O error encountered during iteration, if any.
    pub fn take_error(&mut self) -> Option<SKError> {
        self.error.take()
    }
}

impl Iterator for SKFileIter<'_> {
    type Item = SuperKmer;

    fn next(&mut self) -> Option<SuperKmer> {
        match self.reader.read() {
            Ok(Some(sk)) => Some(sk),
            Ok(None) => None,
            Err(e) => {
                self.error = Some(e);
                None
            }
        }
    }
}

fn read_superkmer<R: Read>(r: &mut R) -> io::Result<Option<SuperKmer>> {
    match SuperKmer::read_from_binary(r) {
        Ok(sk) => Ok(Some(sk)),
        Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(None),
        Err(e) => Err(e),
    }
}

#[cfg(test)]
#[path = "tests/reader.rs"]
mod tests;
