use raw_string::{RawStr, RawString};
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind, Read};
use std::mem::{replace, take};
use std::path::Path;
pub fn read_deps_file(
    file_name: &Path,
    f: impl FnMut(RawString, Vec<RawString>) -> Result<(), Error>,
) -> Result<(), Error> {
    let file = File::open(file_name)
        .map_err(|e| Error::new(e.kind(), format!("Unable to read {file_name:?}: {e}")))?;
    read_deps_file_from(file, f)
}
pub fn read_deps_file_from(
    file: impl Read,
    mut f: impl FnMut(RawString, Vec<RawString>) -> Result<(), Error>,
) -> Result<(), Error> {
    let mut file = BufReader::new(file);
    let mut state = State::default();
    let mut line = RawString::new();
    loop {
        line.clear();
        if file.read_until(b'\n', line.as_mut_bytes())? == 0 {
            break;
        }
        if line.last() == Some(b'\n') {
            line.pop();
        }
        if cfg!(windows) && line.last() == Some(b'\r') {
            line.pop();
        }
        let mut write_offset = 0;
        let mut read_offset = 0;
        loop {
            match memchr::memchr2(b' ', b'\\', line[read_offset..].as_bytes())
                .map(|i| i + read_offset)
            {
                Some(i) if line[i] == b'\\' && i + 1 == line.len() => {
                    state.add_part(&line[write_offset..i]);
                    state.finish_path()?;
                    break;
                }
                Some(i) if line[i] == b'\\' => {
                    let c = line[i + 1];
                    match c {
                        b' ' | b'\\' | b'#' | b'*' | b'[' | b']' | b'|' => {
                            state.add_part(&line[write_offset..i]);
                            write_offset = i + 1;
                        }
                        _ => (), }
                    read_offset = i + 2;
                }
                Some(i) => {
                    debug_assert_eq!(line[i], b' ');
                    state.add_part(&line[write_offset..i]);
                    state.finish_path()?;
                    write_offset = i + 1;
                    read_offset = i + 1;
                }
                None => {
                    state.add_part(&line[write_offset..]);
                    state.finish_deps(&mut f)?;
                    break;
                }
            }
        }
    }
    if state.target.is_none() {
        Ok(())
    } else {
        Err(Error::new(ErrorKind::InvalidData, "Unexpected end of file"))
    }
}
#[derive(Default)]
struct State {
    path: RawString,
    target: Option<RawString>,
    deps: Vec<RawString>,
}
impl State {
    fn add_part(&mut self, s: &RawStr) {
        self.path.push_str(s);
    }
    fn finish_path(&mut self) -> Result<(), Error> {
        if !self.path.is_empty() {
            let mut path = replace(&mut self.path, RawString::new());
            if self.target.is_none() && path.last() == Some(b':') {
                path.pop();
                self.target = Some(path);
            } else if self.target.is_none() {
                return Err(Error::new(
                    ErrorKind::InvalidData,
                    "Rule in dependency file has multiple outputs",
                ));
            } else {
                self.deps.push(path);
            }
        }
        Ok(())
    }
    fn finish_deps(
        &mut self,
        f: &mut impl FnMut(RawString, Vec<RawString>) -> Result<(), Error>,
    ) -> Result<(), Error> {
        self.finish_path()?;
        if let Some(target) = self.target.take() {
            f(target, take(&mut self.deps))?;
        }
        Ok(())
    }
}