use std::{
    io::{BufRead, BufReader},
    str::FromStr,
};

use anyhow::Context;
use log::warn;

use crate::positions::{GenomeRange, GetGenomeRange};

use super::readers::get_reader;

#[derive(Debug)]
pub struct BedRow {
    pub range: GenomeRange,
    pub name: Option<String>,
    pub score: Option<u16>,
    pub strand: Option<bool>,
}

impl FromStr for BedRow {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> anyhow::Result<Self> {
        let v: Vec<&str> = s.split('\t').collect();
        let range: GenomeRange = (
            *v.first()
                .ok_or(anyhow::anyhow!("Can't get contig from: {s}"))?,
            *v.get(1)
                .ok_or(anyhow::anyhow!("Can't get position from: {s}"))?,
            *v.get(2)
                .ok_or(anyhow::anyhow!("Can't get position from: {s}"))?,
        )
            .try_into()
            .context(format!("Can't parse range from: {s}"))?;

        Ok(Self {
            range,
            name: v.get(3).map(|v| Some(v.to_string())).unwrap_or(None),
            score: v.get(4).and_then(|v| v.parse().ok()),
            strand: v.get(5).and_then(|&v| match v {
                "+" => Some(true),
                "-" => Some(false),
                _ => None,
            }),
        })
    }
}

impl GetGenomeRange for BedRow {
    fn range(&self) -> &GenomeRange {
        &self.range
    }
}

pub fn read_bed(path: &str) -> anyhow::Result<Vec<BedRow>> {
    let reader = BufReader::new(get_reader(path)?);

    let mut res = Vec::new();
    for (i, line) in reader.lines().enumerate() {
        match line {
            Ok(line) => {
                if line.starts_with("#") {
                    continue;
                }
                res.push(line.parse().context(format!("Can't parse {line}"))?);
            }
            Err(e) => warn!("Can't read line {i}: {e}"),
        }
    }

    Ok(res)
}
