use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::path::Path;
use std::sync::{Arc, Condvar, Mutex};

#[derive(Default)]
struct State {
    available: u64,
    complete: bool,
}
#[derive(Default)]
struct Shared {
    state: Mutex<State>,
    changed: Condvar,
}
pub struct GrowingFileReader {
    file: File,
    pos: u64,
    shared: Arc<Shared>,
}
pub struct GrowingFileWriter {
    shared: Arc<Shared>,
}

pub fn growing_file(path: &Path) -> io::Result<(GrowingFileReader, GrowingFileWriter)> {
    let shared = Arc::new(Shared::default());
    Ok((
        GrowingFileReader {
            file: File::open(path)?,
            pos: 0,
            shared: Arc::clone(&shared),
        },
        GrowingFileWriter { shared },
    ))
}
impl GrowingFileWriter {
    pub fn add_available(&self, bytes: u64) {
        let mut state = self
            .shared
            .state
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        state.available = state.available.saturating_add(bytes);
        self.shared.changed.notify_all();
    }
    pub fn finish(&self) {
        let mut state = self
            .shared
            .state
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        state.complete = true;
        self.shared.changed.notify_all();
    }
}
impl Drop for GrowingFileWriter {
    fn drop(&mut self) {
        self.finish();
    }
}
impl Read for GrowingFileReader {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        loop {
            let mut state = self
                .shared
                .state
                .lock()
                .unwrap_or_else(std::sync::PoisonError::into_inner);
            while self.pos >= state.available && !state.complete {
                state = self
                    .shared
                    .changed
                    .wait(state)
                    .unwrap_or_else(std::sync::PoisonError::into_inner);
            }
            if self.pos >= state.available {
                return Ok(0);
            }
            let n = usize::try_from((state.available - self.pos).min(buf.len() as u64))
                .unwrap_or(buf.len());
            drop(state);
            let read = self.file.read(&mut buf[..n])?;
            self.pos += read as u64;
            if read > 0 {
                return Ok(read);
            }
        }
    }
}
impl Seek for GrowingFileReader {
    fn seek(&mut self, _: SeekFrom) -> io::Result<u64> {
        Err(io::Error::new(
            io::ErrorKind::Unsupported,
            "federated stream is not seekable while downloading",
        ))
    }
}
