use crate::error::source_quote::SourceQuote;
use std::ops::Range;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Location {
    pub line: usize,
    pub span: std::ops::Range<usize>,
}

impl Location {
    pub fn merge(&self, l2: &Location) -> Self {
        if self.line != l2.line {
            self.clone()
        } else {
            let min = self.span.start.min(l2.span.start);
            let max = self.span.end.max(l2.span.end);
            Location {
                line: self.line,
                span: min..max,
            }
        }
    }
}

impl std::fmt::Display for Location {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}", self.line, self.span.start)
    }
}

#[derive(Debug, Clone)]
pub struct SourceFile {
    pub filename: String,
    pub content: String,
    line_sizes: Vec<usize>,
}

impl SourceFile {
    fn calculate_line_sizes(content: &str) -> Vec<usize> {
        content.split('\n').map(|s| s.len() + 1).collect()
    }

    pub fn open(filename: &str) -> Result<Self, std::io::Error> {
        let content = std::fs::read_to_string(filename)?;
        Ok(Self {
            filename: filename.to_string(),
            line_sizes: Self::calculate_line_sizes(&content),
            content,
        })
    }

    pub fn remaining(&self, pos: usize) -> &str {
        &self.content[pos..]
    }

    pub fn span(&self, range: std::ops::Range<usize>) -> &str {
        &self.content[range]
    }

    pub fn len(&self) -> usize {
        self.content.len()
    }

    pub fn range_to_location(&self, range: std::ops::Range<usize>) -> Location {
        let len = range.end - range.start;
        let mut pos = range.start;
        let mut line = 0;
        for l in &self.line_sizes {
            if pos >= *l {
                line += 1;
                pos -= *l;
            } else {
                break;
            }
        }
        Location {
            line,
            span: pos..pos + len,
        }
    }

    pub fn get_line(&self, i: usize) -> &str {
        if i > self.line_sizes.len() {
            panic!("Out of range line {i}");
        }
        let start = self.line_sizes[..i].iter().sum();
        let len = self.line_sizes[i] - 1;
        &self.content[start..start + len]
    }

    pub fn quote(&self, lines: Range<usize>) -> SourceQuote {
        let mut lines_vec = Vec::new();
        let first_line = lines.start;
        for l in lines {
            lines_vec.push(self.get_line(l));
        }
        SourceQuote::new(&self.filename, lines_vec, first_line)
    }

    pub fn quote_line(&self, line: usize) -> SourceQuote {
        SourceQuote::new(&self.filename, vec![self.get_line(line)], line)
    }
}

impl From<&str> for SourceFile {
    fn from(s: &str) -> SourceFile {
        SourceFile {
            filename: "dummy".to_owned(),
            content: s.to_owned(),
            line_sizes: Self::calculate_line_sizes(s),
        }
    }
}
