use crate::info::IncompressibleReason;
use std::path::Path;
use std::{fmt, io};

#[derive(Debug)]
pub enum SkipReason {
    NotFile,
    AlreadyCompressed,
    NotCompressed,
    EmptyFile,
    TooLarge(u64),
    ReadError(io::Error),
    ZfsFilesystem,
    HasRequiredXattr,
    FsNotSupported,
    HardLink,
}

impl From<IncompressibleReason> for SkipReason {
    fn from(reason: IncompressibleReason) -> SkipReason {
        match reason {
            IncompressibleReason::Empty => SkipReason::EmptyFile,
            IncompressibleReason::TooLarge(size) => SkipReason::TooLarge(size),
            IncompressibleReason::IoError(err) => SkipReason::ReadError(err),
            IncompressibleReason::FsNotSupported => SkipReason::FsNotSupported,
            IncompressibleReason::HasRequiredXattr => SkipReason::HasRequiredXattr,
        }
    }
}

pub trait Progress {
    type Task: Task;

    fn error(&self, path: &Path, message: &str);
    fn file_skipped(&self, _path: &Path, _why: SkipReason) {}
    fn file_task(&self, path: &Path, size: u64) -> Self::Task;
}

pub trait Task {
    fn increment(&self, amt: u64);
    fn error(&self, message: &str);
    fn not_compressible_enough(&self, _path: &Path) {}
}

impl<P: Progress> Progress for &'_ P {
    type Task = P::Task;

    fn error(&self, path: &Path, message: &str) {
        P::error(self, path, message)
    }

    fn file_skipped(&self, path: &Path, why: SkipReason) {
        P::file_skipped(self, path, why)
    }

    fn file_task(&self, path: &Path, size: u64) -> Self::Task {
        P::file_task(self, path, size)
    }
}

impl<T: Task> Task for &'_ T {
    fn increment(&self, amt: u64) {
        T::increment(self, amt)
    }

    fn error(&self, message: &str) {
        T::error(self, message)
    }

    fn not_compressible_enough(&self, path: &Path) {
        T::not_compressible_enough(self, path)
    }
}

impl fmt::Display for SkipReason {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            SkipReason::NotFile => write!(f, "Not a file"),
            SkipReason::AlreadyCompressed => write!(f, "Already compressed"),
            SkipReason::NotCompressed => write!(f, "Not compressed"),
            SkipReason::TooLarge(size) => write!(f, "File too large: {size} > {}", u32::MAX),
            SkipReason::ReadError(ref err) => write!(f, "Read error: {err}"),
            SkipReason::ZfsFilesystem => write!(f, "ZFS filesystem (not supported)"),
            SkipReason::HasRequiredXattr => write!(f, "Compression xattrs already present"),
            SkipReason::FsNotSupported => write!(f, "Filesystem does not support compression"),
            SkipReason::EmptyFile => write!(f, "Empty file"),
            SkipReason::HardLink => write!(f, "Other hard links exist to file"),
        }
    }
}

impl From<io::Error> for SkipReason {
    fn from(err: io::Error) -> SkipReason {
        SkipReason::ReadError(err)
    }
}
