Documentation
use anyhow::{anyhow, bail, Result};
use std::path::Path;
use std::path::PathBuf;

use super::super::fs::{copy_host_to_image, copy_image_to_host, copy_image_to_image, is_dir};
use super::super::types::{PartitionTarget, PathKind};
use super::super::utils::{host_path, normalize_image_path, path_kind};

pub fn cp(
    disk: &Path,
    target: &PartitionTarget,
    src: &str,
    dst: &str,
    recursive: bool,
    force: bool,
    _preserve: bool,
) -> Result<()> {
    let overwrite = force;
    let src_kind = path_kind(src);
    let dst_kind = path_kind(dst);

    match (src_kind, dst_kind) {
        (PathKind::Host, PathKind::Image) => {
            let host = host_path(src)?;
            let image = normalize_image_path(dst);
            let image = resolve_host_to_image_dst(disk, target, &host, &image)?;
            copy_host_to_image(disk, target, &host, &image, recursive, overwrite)?;
            println!("{}", image);
            Ok(())
        }
        (PathKind::Image, PathKind::Host) => {
            let image = normalize_image_path(src);
            let host = host_path(dst)?;
            let host = resolve_image_to_host_dst(&image, &host)?;
            copy_image_to_host(disk, target, &image, &host, recursive, overwrite)?;
            println!("{}", host.display());
            Ok(())
        }
        (PathKind::Image, PathKind::Image) => {
            let src_image = normalize_image_path(src);
            let dst_image = normalize_image_path(dst);
            let dst_image = resolve_image_to_image_dst(disk, target, &src_image, &dst_image)?;
            copy_image_to_image(disk, target, &src_image, &dst_image, recursive, overwrite)?;
            println!("{}", dst_image);
            Ok(())
        }
        _ => bail!("host -> host is not supported by xtool disk"),
    }
}

fn resolve_host_to_image_dst(
    disk: &Path,
    target: &PartitionTarget,
    host: &Path,
    image: &str,
) -> Result<String> {
    let mut is_dir_dst = image.ends_with('/');
    if !is_dir_dst {
        is_dir_dst = is_dir(disk, target, image).unwrap_or(false);
    }

    if image.ends_with('/') && !is_dir_dst {
        bail!("destination is not a directory");
    }

    if !is_dir_dst {
        return Ok(image.to_string());
    }

    let name = host
        .file_name()
        .and_then(|s| s.to_str())
        .ok_or_else(|| anyhow!("invalid host path"))?;

    if image == "/" {
        return Ok(format!("/{}", name));
    }
    Ok(format!("{}/{}", image.trim_end_matches('/'), name))
}

fn resolve_image_to_host_dst(image: &str, host: &Path) -> Result<PathBuf> {
    let mut is_dir_dst = host.as_os_str().to_string_lossy().ends_with('/');
    if !is_dir_dst && let Ok(meta) = std::fs::metadata(host) {
        is_dir_dst = meta.is_dir();
    }

    if host.as_os_str().to_string_lossy().ends_with('/') && !is_dir_dst {
        bail!("destination is not a directory");
    }

    if !is_dir_dst {
        return Ok(host.to_path_buf());
    }

    let image = image.trim_end_matches('/');
    let name = image
        .rsplit('/').next()
        .filter(|s| !s.is_empty())
        .ok_or_else(|| anyhow!("invalid image path"))?;

    if name == "." || name == ".." {
        bail!("invalid image path");
    }

    let dst = host.join(name);
    Ok(dst)
}

fn resolve_image_to_image_dst(
    disk: &Path,
    target: &PartitionTarget,
    src: &str,
    dst: &str,
) -> Result<String> {
    let mut is_dir_dst = dst.ends_with('/');
    if !is_dir_dst {
        is_dir_dst = is_dir(disk, target, dst).unwrap_or(false);
    }

    if dst.ends_with('/') && !is_dir_dst {
        bail!("destination is not a directory");
    }

    if !is_dir_dst {
        return Ok(dst.to_string());
    }

    let src = src.trim_end_matches('/');
    let name = src
        .rsplit('/').next()
        .filter(|s| !s.is_empty())
        .ok_or_else(|| anyhow!("invalid image path"))?;

    if name == "." || name == ".." {
        bail!("invalid image path");
    }

    if dst == "/" {
        return Ok(format!("/{}", name));
    }
    Ok(format!("{}/{}", dst.trim_end_matches('/'), name))
}