use crate::pristine::InodeMetadata;
use crate::working_copy::{memory, WorkingCopy, WorkingCopyRead};
use crate::{working_copy, Inode};
use std::fs::File;
use std::io;
use std::io::BufWriter;
use std::time::SystemTime;
use thiserror::Error;

#[inline]
fn dispatch_callfn<F, T, R>(f: F, v: T) -> R
where
    T: WorkingCopyRead,
    F: FnOnce(T) -> R,
{
    f(v)
}

macro_rules! dispatch_fallible {
    ($s:expr, $f:expr) => {
        match $s {
            #[cfg(feature = "ondisk-repos")]
            Any::FileSystem(v) => Ok(dispatch_callfn($f, v)?),
            Any::Memory(v) => Ok(dispatch_callfn($f, v)?),
            Any::Sink(v) => Ok(dispatch_callfn($f, v)?),
        }
    };
}

#[derive(Clone)]
pub enum Any {
    #[cfg(feature = "ondisk-repos")]
    FileSystem(working_copy::FileSystem),
    Memory(working_copy::Memory),
    Sink(working_copy::Sink),
}

#[derive(Debug, Error)]
pub enum Error {
    #[error("{0}")]
    Io(#[from] io::Error),
    #[error("{0}")]
    Memory(#[from] memory::Error),
}

pub enum Writer {
    File(BufWriter<File>),
    Memory(memory::Writer),
    Sink(io::Sink),
}

impl From<BufWriter<File>> for Writer {
    fn from(value: BufWriter<File>) -> Self {
        Writer::File(value)
    }
}

impl From<memory::Writer> for Writer {
    fn from(value: memory::Writer) -> Self {
        Writer::Memory(value)
    }
}

impl From<io::Sink> for Writer {
    fn from(value: io::Sink) -> Self {
        Writer::Sink(value)
    }
}

impl io::Write for Writer {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match self {
            Writer::File(v) => v.write(buf),
            Writer::Memory(v) => v.write(buf),
            Writer::Sink(v) => v.write(buf),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match self {
            Writer::File(v) => v.flush(),
            Writer::Memory(v) => v.flush(),
            Writer::Sink(v) => v.flush(),
        }
    }
}

impl WorkingCopyRead for Any {
    type Error = Error;

    fn file_metadata(&self, file: &str) -> Result<InodeMetadata, Self::Error> {
        dispatch_fallible!(self, |v| v.file_metadata(file))
    }

    fn read_file(&self, file: &str, buffer: &mut Vec<u8>) -> Result<(), Self::Error> {
        dispatch_fallible!(self, |v| v.read_file(file, buffer))
    }

    fn modified_time(&self, file: &str) -> Result<SystemTime, Self::Error> {
        dispatch_fallible!(self, |v| v.modified_time(file))
    }
}

impl WorkingCopy for Any {
    fn create_dir_all(&self, path: &str) -> Result<(), Self::Error> {
        dispatch_fallible!(self, |v| v.create_dir_all(path))
    }

    fn remove_path(&self, name: &str, rec: bool) -> Result<(), Self::Error> {
        dispatch_fallible!(self, |v| v.remove_path(name, rec))
    }

    fn rename(&self, former: &str, new: &str) -> Result<(), Self::Error> {
        dispatch_fallible!(self, |v| v.rename(former, new))
    }

    fn set_permissions(&self, name: &str, permissions: u16) -> Result<(), Self::Error> {
        dispatch_fallible!(self, |v| v.set_permissions(name, permissions))
    }

    type Writer = Writer;

    fn write_file(&self, file: &str, inode: Inode) -> Result<Self::Writer, Self::Error> {
        dispatch_fallible!(self, |v| v.write_file(file, inode).map(|wr| wr.into()))
    }
}