use std::path::{Path, PathBuf};

use crate::core::{GenotypeChunk, GenotypeData, SampleInfo};
use crate::error::{BunbunError, Result};

/// Every format parser implements this trait.
pub trait GenotypeReader: Send + Sync {
    fn format_name(&self) -> &'static str;
    fn read_all(&self) -> Result<GenotypeData>;
    fn read_samples(&self) -> Result<SampleInfo>;
    fn n_variants(&self) -> Result<usize>;
    fn read_chunked(&self, chunk_size: usize) -> Result<ChunkedReader>;
}

/// Iterator over `GenotypeChunk` values. Format-agnostic.
pub struct ChunkedReader {
    inner: Box<dyn Iterator<Item = Result<GenotypeChunk>> + Send>,
    pub samples: SampleInfo,
    pub source_format: &'static str,
}

impl ChunkedReader {
    pub fn new(
        inner: Box<dyn Iterator<Item = Result<GenotypeChunk>> + Send>,
        samples: SampleInfo,
        source_format: &'static str,
    ) -> Self {
        Self { inner, samples, source_format }
    }
}

impl Iterator for ChunkedReader {
    type Item = Result<GenotypeChunk>;
    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Format { Plink, Plink2, Vcf, Bgen }

impl Format {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Plink => "plink",
            Self::Plink2 => "plink2",
            Self::Vcf => "vcf",
            Self::Bgen => "bgen",
        }
    }
}

pub struct FormatDetector;

impl FormatDetector {
    pub fn detect(path: &Path) -> Result<Format> {
        let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
        let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
        match ext {
            "bed" | "bim" | "fam" => Ok(Format::Plink),
            "pgen" | "pvar" | "psam" => Ok(Format::Plink2),
            "bgen" | "sample" => Ok(Format::Bgen),
            "vcf" => Ok(Format::Vcf),
            "gz" if stem.ends_with(".vcf") => Ok(Format::Vcf),
            "gz" if stem.ends_with(".pvar") => Ok(Format::Plink2),
            _ => Err(BunbunError::MalformedHeader {
                format: "auto-detect", offset: 0,
                detail: format!("unrecognized extension: .{ext}"),
            }),
        }
    }

    pub fn resolve_plink(path: &Path) -> (PathBuf, PathBuf, PathBuf) {
        let stem = path.with_extension("");
        (stem.with_extension("bed"), stem.with_extension("bim"), stem.with_extension("fam"))
    }

    pub fn resolve_plink2(path: &Path) -> (PathBuf, PathBuf, PathBuf) {
        let stem = path.with_extension("");
        (stem.with_extension("pgen"), stem.with_extension("pvar"), stem.with_extension("psam"))
    }

    pub fn resolve_bgen(path: &Path) -> (PathBuf, PathBuf) {
        let stem = path.with_extension("");
        (stem.with_extension("bgen"), stem.with_extension("sample"))
    }
}
