use std::cmp::min;
use std::fmt::Debug;
use std::fs::File;
use std::io;
use std::io::Seek;
use std::io::SeekFrom;
use std::path::PathBuf;
use std::sync::Arc;
use async_trait::async_trait;
use base::info;
use base::AsRawDescriptors;
use base::FileAllocate;
use base::FileReadWriteAtVolatile;
use base::FileSetLen;
use cros_async::BackingMemory;
use cros_async::Executor;
use cros_async::IoSource;
use cros_async::MemRegionIter;
use thiserror::Error as ThisError;
mod asynchronous;
#[allow(unused)]
pub(crate) use asynchronous::AsyncDiskFileWrapper;
#[cfg(feature = "qcow")]
mod qcow;
#[cfg(feature = "qcow")]
pub use qcow::QcowFile;
#[cfg(feature = "qcow")]
pub use qcow::QCOW_MAGIC;
mod sys;
#[cfg(feature = "composite-disk")]
mod composite;
#[cfg(feature = "composite-disk")]
use composite::CompositeDiskFile;
#[cfg(feature = "composite-disk")]
use composite::CDISK_MAGIC;
#[cfg(feature = "composite-disk")]
mod gpt;
#[cfg(feature = "composite-disk")]
pub use composite::create_composite_disk;
#[cfg(feature = "composite-disk")]
pub use composite::create_zero_filler;
#[cfg(feature = "composite-disk")]
pub use composite::Error as CompositeError;
#[cfg(feature = "composite-disk")]
pub use composite::ImagePartitionType;
#[cfg(feature = "composite-disk")]
pub use composite::PartitionInfo;
#[cfg(feature = "composite-disk")]
pub use gpt::Error as GptError;
#[cfg(feature = "android-sparse")]
mod android_sparse;
#[cfg(feature = "android-sparse")]
use android_sparse::AndroidSparse;
#[cfg(feature = "android-sparse")]
use android_sparse::SPARSE_HEADER_MAGIC;
use sys::read_from_disk;
#[cfg(feature = "zstd")]
mod zstd;
#[cfg(feature = "zstd")]
use zstd::ZstdDisk;
#[cfg(feature = "zstd")]
use zstd::ZSTD_FRAME_MAGIC;
#[cfg(feature = "zstd")]
use zstd::ZSTD_SKIPPABLE_MAGIC_HIGH;
#[cfg(feature = "zstd")]
use zstd::ZSTD_SKIPPABLE_MAGIC_LOW;
const MAX_NESTING_DEPTH: u32 = 10;
#[derive(ThisError, Debug)]
pub enum Error {
    #[error("failed to create block device: {0}")]
    BlockDeviceNew(base::Error),
    #[error("requested file conversion not supported")]
    ConversionNotSupported,
    #[cfg(feature = "android-sparse")]
    #[error("failure in android sparse disk: {0}")]
    CreateAndroidSparseDisk(android_sparse::Error),
    #[cfg(feature = "composite-disk")]
    #[error("failure in composite disk: {0}")]
    CreateCompositeDisk(composite::Error),
    #[cfg(feature = "zstd")]
    #[error("failure in zstd disk: {0}")]
    CreateZstdDisk(anyhow::Error),
    #[error("failure creating single file disk: {0}")]
    CreateSingleFileDisk(cros_async::AsyncError),
    #[error("failed to set O_DIRECT on disk image: {0}")]
    DirectFailed(base::Error),
    #[error("failure with fdatasync: {0}")]
    Fdatasync(cros_async::AsyncError),
    #[error("failure with fsync: {0}")]
    Fsync(cros_async::AsyncError),
    #[error("failed to lock file: {0}")]
    LockFileFailure(base::Error),
    #[error("failure with fdatasync: {0}")]
    IoFdatasync(io::Error),
    #[error("failure with flush: {0}")]
    IoFlush(io::Error),
    #[error("failure with fsync: {0}")]
    IoFsync(io::Error),
    #[error("failure to punch hole: {0}")]
    IoPunchHole(io::Error),
    #[error("checking host fs type: {0}")]
    HostFsType(base::Error),
    #[error("maximum disk nesting depth exceeded")]
    MaxNestingDepthExceeded,
    #[error("failed to open disk file \"{0}\": {1}")]
    OpenFile(String, base::Error),
    #[error("failure to punch hole: {0}")]
    PunchHole(cros_async::AsyncError),
    #[error("failure to punch hole for block device file: {0}")]
    PunchHoleBlockDeviceFile(base::Error),
    #[cfg(feature = "qcow")]
    #[error("failure in qcow: {0}")]
    QcowError(qcow::Error),
    #[error("failed to read data: {0}")]
    ReadingData(io::Error),
    #[error("failed to read header: {0}")]
    ReadingHeader(io::Error),
    #[error("failed to read to memory: {0}")]
    ReadToMem(cros_async::AsyncError),
    #[error("failed to seek file: {0}")]
    SeekingFile(io::Error),
    #[error("failed to set file size: {0}")]
    SettingFileSize(io::Error),
    #[error("unknown disk type")]
    UnknownType,
    #[error("failed to write from memory: {0}")]
    WriteFromMem(cros_async::AsyncError),
    #[error("failed to write from vec: {0}")]
    WriteFromVec(cros_async::AsyncError),
    #[error("failed to write zeroes: {0}")]
    WriteZeroes(io::Error),
    #[error("failed to write data: {0}")]
    WritingData(io::Error),
    #[error("failed to convert to async: {0}")]
    ToAsync(cros_async::AsyncError),
    #[cfg(windows)]
    #[error("failed to set disk file sparse: {0}")]
    SetSparseFailure(io::Error),
    #[error("failure with guest memory access: {0}")]
    GuestMemory(cros_async::mem::Error),
    #[error("unsupported operation")]
    UnsupportedOperation,
}
pub type Result<T> = std::result::Result<T, Error>;
pub trait DiskGetLen {
    fn get_len(&self) -> io::Result<u64>;
}
impl DiskGetLen for File {
    fn get_len(&self) -> io::Result<u64> {
        let mut s = self;
        let orig_seek = s.stream_position()?;
        let end = s.seek(SeekFrom::End(0))?;
        s.seek(SeekFrom::Start(orig_seek))?;
        Ok(end)
    }
}
pub trait DiskFile:
    FileSetLen + DiskGetLen + FileReadWriteAtVolatile + ToAsyncDisk + Send + AsRawDescriptors + Debug
{
    fn try_clone(&self) -> io::Result<Box<dyn DiskFile>> {
        Err(io::Error::new(
            io::ErrorKind::Unsupported,
            "unsupported operation",
        ))
    }
}
pub trait ToAsyncDisk: AsRawDescriptors + DiskGetLen + Send {
    fn to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>>;
}
impl ToAsyncDisk for File {
    fn to_async_disk(self: Box<Self>, ex: &Executor) -> Result<Box<dyn AsyncDisk>> {
        Ok(Box::new(SingleFileDisk::new(*self, ex)?))
    }
}
#[derive(Debug, PartialEq, Eq)]
pub enum ImageType {
    Raw,
    Qcow2,
    CompositeDisk,
    AndroidSparse,
    Zstd,
}
pub fn detect_image_type(file: &File, overlapped_mode: bool) -> Result<ImageType> {
    let mut f = file;
    let disk_size = f.get_len().map_err(Error::SeekingFile)?;
    let orig_seek = f.stream_position().map_err(Error::SeekingFile)?;
    info!("disk size {}", disk_size);
    const MAGIC_BLOCK_SIZE: usize = 4096;
    #[repr(align(4096))]
    struct BlockAlignedBuffer {
        data: [u8; MAGIC_BLOCK_SIZE],
    }
    let mut magic = BlockAlignedBuffer {
        data: [0u8; MAGIC_BLOCK_SIZE],
    };
    let magic_read_len = if disk_size > MAGIC_BLOCK_SIZE as u64 {
        MAGIC_BLOCK_SIZE
    } else {
        disk_size as usize
    };
    read_from_disk(f, 0, &mut magic.data[0..magic_read_len], overlapped_mode)?;
    f.seek(SeekFrom::Start(orig_seek))
        .map_err(Error::SeekingFile)?;
    #[cfg(feature = "composite-disk")]
    if let Some(cdisk_magic) = magic.data.get(0..CDISK_MAGIC.len()) {
        if cdisk_magic == CDISK_MAGIC.as_bytes() {
            return Ok(ImageType::CompositeDisk);
        }
    }
    #[allow(unused_variables)] if let Some(magic4) = magic
        .data
        .get(0..4)
        .and_then(|v| <&[u8] as std::convert::TryInto<[u8; 4]>>::try_into(v).ok())
    {
        #[cfg(feature = "qcow")]
        if magic4 == QCOW_MAGIC.to_be_bytes() {
            return Ok(ImageType::Qcow2);
        }
        #[cfg(feature = "android-sparse")]
        if magic4 == SPARSE_HEADER_MAGIC.to_le_bytes() {
            return Ok(ImageType::AndroidSparse);
        }
        #[cfg(feature = "zstd")]
        if u32::from_le_bytes(magic4) == ZSTD_FRAME_MAGIC
            || (u32::from_le_bytes(magic4) >= ZSTD_SKIPPABLE_MAGIC_LOW
                && u32::from_le_bytes(magic4) <= ZSTD_SKIPPABLE_MAGIC_HIGH)
        {
            return Ok(ImageType::Zstd);
        }
    }
    Ok(ImageType::Raw)
}
impl DiskFile for File {
    fn try_clone(&self) -> io::Result<Box<dyn DiskFile>> {
        Ok(Box::new(self.try_clone()?))
    }
}
pub struct DiskFileParams {
    pub path: PathBuf,
    pub is_read_only: bool,
    pub is_sparse_file: bool,
    pub is_overlapped: bool,
    pub is_direct: bool,
    pub lock: bool,
    pub depth: u32,
}
pub fn open_disk_file(params: DiskFileParams) -> Result<Box<dyn DiskFile>> {
    if params.depth > MAX_NESTING_DEPTH {
        return Err(Error::MaxNestingDepthExceeded);
    }
    let raw_image = sys::open_raw_disk_image(¶ms)?;
    let image_type = detect_image_type(&raw_image, params.is_overlapped)?;
    Ok(match image_type {
        ImageType::Raw => {
            sys::apply_raw_disk_file_options(&raw_image, params.is_sparse_file)?;
            Box::new(raw_image) as Box<dyn DiskFile>
        }
        #[cfg(feature = "qcow")]
        ImageType::Qcow2 => Box::new(QcowFile::from(raw_image, params).map_err(Error::QcowError)?)
            as Box<dyn DiskFile>,
        #[cfg(feature = "composite-disk")]
        ImageType::CompositeDisk => {
            Box::new(
                CompositeDiskFile::from_file(raw_image, params)
                    .map_err(Error::CreateCompositeDisk)?,
            ) as Box<dyn DiskFile>
        }
        #[cfg(feature = "android-sparse")]
        ImageType::AndroidSparse => {
            Box::new(AndroidSparse::from_file(raw_image).map_err(Error::CreateAndroidSparseDisk)?)
                as Box<dyn DiskFile>
        }
        #[cfg(feature = "zstd")]
        ImageType::Zstd => Box::new(ZstdDisk::from_file(raw_image).map_err(Error::CreateZstdDisk)?)
            as Box<dyn DiskFile>,
        #[allow(unreachable_patterns)]
        _ => return Err(Error::UnknownType),
    })
}
#[async_trait(?Send)]
pub trait AsyncDisk: DiskGetLen + FileSetLen + FileAllocate {
    async fn flush(&self) -> Result<()>;
    async fn fsync(&self) -> Result<()>;
    async fn fdatasync(&self) -> Result<()>;
    async fn read_to_mem<'a>(
        &'a self,
        file_offset: u64,
        mem: Arc<dyn BackingMemory + Send + Sync>,
        mem_offsets: cros_async::MemRegionIter<'a>,
    ) -> Result<usize>;
    async fn write_from_mem<'a>(
        &'a self,
        file_offset: u64,
        mem: Arc<dyn BackingMemory + Send + Sync>,
        mem_offsets: cros_async::MemRegionIter<'a>,
    ) -> Result<usize>;
    async fn punch_hole(&self, file_offset: u64, length: u64) -> Result<()>;
    async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()>;
    async fn read_double_buffered(&self, file_offset: u64, buf: &mut [u8]) -> Result<usize> {
        let backing_mem = Arc::new(cros_async::VecIoWrapper::from(vec![0u8; buf.len()]));
        let region = cros_async::MemRegion {
            offset: 0,
            len: buf.len(),
        };
        let n = self
            .read_to_mem(
                file_offset,
                backing_mem.clone(),
                MemRegionIter::new(&[region]),
            )
            .await?;
        backing_mem
            .get_volatile_slice(region)
            .expect("BUG: the VecIoWrapper shrank?")
            .sub_slice(0, n)
            .expect("BUG: read_to_mem return value too large?")
            .copy_to(buf);
        Ok(n)
    }
    async fn write_double_buffered(&self, file_offset: u64, buf: &[u8]) -> Result<usize> {
        let backing_mem = Arc::new(cros_async::VecIoWrapper::from(buf.to_vec()));
        let region = cros_async::MemRegion {
            offset: 0,
            len: buf.len(),
        };
        self.write_from_mem(
            file_offset,
            backing_mem,
            cros_async::MemRegionIter::new(&[region]),
        )
        .await
    }
}
pub struct SingleFileDisk {
    inner: IoSource<File>,
    #[cfg(any(target_os = "android", target_os = "linux"))]
    is_block_device_file: bool,
}
impl DiskGetLen for SingleFileDisk {
    fn get_len(&self) -> io::Result<u64> {
        self.inner.as_source().get_len()
    }
}
impl FileSetLen for SingleFileDisk {
    fn set_len(&self, len: u64) -> io::Result<()> {
        self.inner.as_source().set_len(len)
    }
}
impl FileAllocate for SingleFileDisk {
    fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
        self.inner.as_source().allocate(offset, len)
    }
}
#[async_trait(?Send)]
impl AsyncDisk for SingleFileDisk {
    async fn flush(&self) -> Result<()> {
        Ok(())
    }
    async fn fsync(&self) -> Result<()> {
        self.inner.fsync().await.map_err(Error::Fsync)
    }
    async fn fdatasync(&self) -> Result<()> {
        self.inner.fdatasync().await.map_err(Error::Fdatasync)
    }
    async fn read_to_mem<'a>(
        &'a self,
        file_offset: u64,
        mem: Arc<dyn BackingMemory + Send + Sync>,
        mem_offsets: cros_async::MemRegionIter<'a>,
    ) -> Result<usize> {
        self.inner
            .read_to_mem(Some(file_offset), mem, mem_offsets)
            .await
            .map_err(Error::ReadToMem)
    }
    async fn write_from_mem<'a>(
        &'a self,
        file_offset: u64,
        mem: Arc<dyn BackingMemory + Send + Sync>,
        mem_offsets: cros_async::MemRegionIter<'a>,
    ) -> Result<usize> {
        self.inner
            .write_from_mem(Some(file_offset), mem, mem_offsets)
            .await
            .map_err(Error::WriteFromMem)
    }
    async fn punch_hole(&self, file_offset: u64, length: u64) -> Result<()> {
        #[cfg(any(target_os = "android", target_os = "linux"))]
        if self.is_block_device_file {
            return base::linux::discard_block(self.inner.as_source(), file_offset, length)
                .map_err(Error::PunchHoleBlockDeviceFile);
        }
        self.inner
            .punch_hole(file_offset, length)
            .await
            .map_err(Error::PunchHole)
    }
    async fn write_zeroes_at(&self, file_offset: u64, length: u64) -> Result<()> {
        if self
            .inner
            .write_zeroes_at(file_offset, length)
            .await
            .is_ok()
        {
            return Ok(());
        }
        let buf_size = min(length, 0x10000);
        let mut nwritten = 0;
        while nwritten < length {
            let remaining = length - nwritten;
            let write_size = min(remaining, buf_size) as usize;
            let buf = vec![0u8; write_size];
            nwritten += self
                .inner
                .write_from_vec(Some(file_offset + nwritten), buf)
                .await
                .map(|(n, _)| n as u64)
                .map_err(Error::WriteFromVec)?;
        }
        Ok(())
    }
}