Documentation
use anyhow::{anyhow, bail, Result};
use crate::disk::fatfs::{self,
    FileSystem, FormatVolumeOptions, FsOptions, FatType, OemCpConverter, ReadWriteSeek, StdIoWrapper,
    TimeProvider,
};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;

use super::super::io::PartitionIo;
use super::super::types::{DirEntry, PartitionTarget};
use super::super::utils::{format_fat_label, iter_path_components, normalize_image_path};
use super::FsOps;

pub type FatFs = FileSystem<StdIoWrapper<PartitionIo>>;

pub struct FatOps<'a> {
    fs: &'a mut FatFs,
}

pub fn mkfs_fat32(disk: &Path, target: &PartitionTarget, label: Option<&str>) -> Result<()> {
    let file = std::fs::OpenOptions::new()
        .read(true)
        .write(true)
        .open(disk)
        .map_err(|e| anyhow!("failed to open disk {}: {e}", disk.display()))?;

    let mut opts = FormatVolumeOptions::new().fat_type(FatType::Fat32);
    if let Some(label) = label {
        opts = opts.volume_label(format_fat_label(label)?);
    }

    let mut io = StdIoWrapper::new(PartitionIo::new(
        file,
        target.offset_bytes,
        target.size_bytes,
    ));
    fatfs::format_volume(&mut io, opts).map_err(|e| anyhow!("mkfs fat32 failed: {e}"))?;
    Ok(())
}

pub fn with_fat<R>(
    disk: &Path,
    target: &PartitionTarget,
    f: impl for<'a> FnOnce(FatOps<'a>) -> Result<R>,
) -> Result<R> {
    let file = std::fs::OpenOptions::new()
        .read(true)
        .write(true)
        .open(disk)
        .map_err(|e| anyhow!("failed to open disk {}: {e}", disk.display()))?;
    let io = StdIoWrapper::new(PartitionIo::new(
        file,
        target.offset_bytes,
        target.size_bytes,
    ));
    let mut fs = FileSystem::new(io, FsOptions::new())
        .map_err(|e| anyhow!("mount fat failed: {e}"))?;

    let result = f(FatOps { fs: &mut fs })?;
    fs.unmount().map_err(|e| anyhow!("fat unmount failed: {e}"))?;
    Ok(result)
}

impl FsOps for FatOps<'_> {
    fn list_dir(&mut self, path: &str) -> Result<Vec<DirEntry>> {
        let root = self.fs.root_dir();
        let dir = if path == "/" || path.is_empty() {
            root
        } else {
            root.open_dir(path).map_err(|e| anyhow!("open dir failed: {e}"))?
        };

        let mut out = Vec::new();
        for entry in dir.iter() {
            let entry = entry.map_err(|e| anyhow!("iter failed: {e:?}"))?;
            let name = entry.file_name();
            if name == "." || name == ".." {
                continue;
            }
            out.push(DirEntry {
                name,
                is_dir: entry.is_dir(),
            });
        }
        out.sort_by(|a, b| a.name.cmp(&b.name));
        Ok(out)
    }

    fn read_file(&mut self, path: &str, offset: u64, bytes: Option<usize>) -> Result<Vec<u8>> {
        let root = self.fs.root_dir();
        let mut file = root
            .open_file(path)
            .map_err(|e| anyhow!("open file failed: {e}"))?;

        file.seek(SeekFrom::Start(offset))
            .map_err(|e| anyhow!("seek failed: {e}"))?;

        let mut data = Vec::new();
        if let Some(n) = bytes {
            let mut buf = vec![0u8; n];
            let read = file.read(&mut buf).map_err(|e| anyhow!("read failed: {e}"))?;
            buf.truncate(read);
            data.extend_from_slice(&buf);
        } else {
            file.read_to_end(&mut data)
                .map_err(|e| anyhow!("read failed: {e}"))?;
        }
        Ok(data)
    }

    fn write_file(&mut self, path: &str, data: &[u8], force: bool) -> Result<()> {
        let root = self.fs.root_dir();
        let mut file = match root.open_file(path) {
            Ok(mut f) => {
                if !force {
                    bail!("destination exists, use -f to overwrite");
                }
                f.truncate().map_err(|e| anyhow!("truncate failed: {e}"))?;
                f
            }
            Err(_) => root
                .create_file(path)
                .map_err(|e| anyhow!("create file failed: {e}"))?,
        };
        file.write_all(data)
            .map_err(|e| anyhow!("write failed: {e}"))?;
        Ok(())
    }

    fn mkdir(&mut self, path: &str, parents: bool) -> Result<()> {
        let root = self.fs.root_dir();
        if parents {
            for p in iter_path_components(path) {
                let _ = root.create_dir(&p);
            }
            return Ok(());
        }
        root.create_dir(path)
            .map_err(|e| anyhow!("mkdir failed: {e}"))?;
        Ok(())
    }

    fn rm(&mut self, path: &str, recursive: bool) -> Result<()> {
        let root = self.fs.root_dir();
        if recursive {
            return remove_fat_recursive(&root, path);
        }
        root.remove(path)
            .map_err(|e| anyhow!("remove failed: {e}"))?;
        Ok(())
    }

    fn mv(&mut self, src: &str, dst: &str, force: bool) -> Result<()> {
        let root = self.fs.root_dir();
        if !force {
            if root.open_file(dst).is_ok() || root.open_dir(dst).is_ok() {
                bail!("destination exists, use -f to overwrite");
            }
        } else {
            let _ = root.remove(dst);
        }
        root.rename(src, &root, dst)
            .map_err(|e| anyhow!("rename failed: {e}"))?;
        Ok(())
    }

    fn is_dir(&mut self, path: &str) -> Result<bool> {
        let root = self.fs.root_dir();
        let path = normalize_image_path(path);
        Ok(root.open_dir(&path).is_ok())
    }
}

fn remove_fat_recursive<IO, TP, OCC>(root: &fatfs::Dir<IO, TP, OCC>, path: &str) -> Result<()>
where
    IO: ReadWriteSeek,
    TP: TimeProvider,
    OCC: OemCpConverter,
{
    if let Ok(dir) = root.open_dir(path) {
        for entry in dir.iter() {
            let entry = entry.map_err(|e| anyhow!("iter failed: {e:?}"))?;
            let name = entry.file_name();
            if name == "." || name == ".." {
                continue;
            }
            let child = format!("{}/{}", path.trim_end_matches('/'), name);
            if entry.is_dir() {
                remove_fat_recursive(root, &child)?;
            } else {
                root.remove(&child)
                    .map_err(|e| anyhow!("remove failed: {e:?}"))?;
            }
        }
        root.remove(path)
            .map_err(|e| anyhow!("remove failed: {e:?}"))?;
        return Ok(());
    }
    root.remove(path)
        .map_err(|e| anyhow!("remove failed: {e:?}"))?;
    Ok(())
}