use base::AsRawDescriptor;
use base::Event;
use base::Protection;
use base::RawDescriptor;
use base::Tube;
use base::TubeError;
use hypervisor::Datamatch;
use hypervisor::MemCacheType;
use remain::sorted;
use resources::Alloc;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error;
use vm_memory::GuestAddress;
use crate::IoEventUpdateRequest;
use crate::VmMemoryDestination;
use crate::VmMemoryRegionId;
use crate::VmMemoryRequest;
use crate::VmMemoryResponse;
use crate::VmMemoryResponseError;
use crate::VmMemorySource;
#[derive(Error, Debug)]
#[sorted]
pub enum ApiClientError {
    #[error("API client tube recv failed: {0}")]
    Recv(TubeError),
    #[error("Request failed: {0}")]
    RequestFailed(#[source] anyhow::Error),
    #[error("API client tube send failed: {0}")]
    Send(TubeError),
    #[error("API client tube sending FDs failed: {0}")]
    SendFds(TubeError),
    #[error("Unexpected tube response")]
    UnexpectedResponse,
}
pub type Result<T> = std::result::Result<T, ApiClientError>;
#[derive(Serialize, Deserialize)]
pub struct VmMemoryClient {
    tube: Tube,
}
impl VmMemoryClient {
    pub fn new(tube: Tube) -> Self {
        VmMemoryClient { tube }
    }
    fn request(&self, request: &VmMemoryRequest) -> Result<VmMemoryResponse> {
        self.tube.send(request).map_err(ApiClientError::Send)?;
        self.tube
            .recv::<VmMemoryResponse>()
            .map_err(ApiClientError::Recv)
    }
    fn request_unit(&self, request: &VmMemoryRequest) -> Result<()> {
        match self.request(request)? {
            VmMemoryResponse::Ok => Ok(()),
            VmMemoryResponse::Err(VmMemoryResponseError(e)) => {
                Err(ApiClientError::RequestFailed(e))
            }
            _other => Err(ApiClientError::UnexpectedResponse),
        }
    }
    pub fn prepare_shared_memory_region(&self, alloc: Alloc, cache: MemCacheType) -> Result<()> {
        self.request_unit(&VmMemoryRequest::PrepareSharedMemoryRegion { alloc, cache })
    }
    pub fn register_memory(
        &self,
        source: VmMemorySource,
        dest: VmMemoryDestination,
        prot: Protection,
        cache: MemCacheType,
    ) -> Result<VmMemoryRegionId> {
        let request = VmMemoryRequest::RegisterMemory {
            source,
            dest,
            prot,
            cache,
        };
        match self.request(&request)? {
            VmMemoryResponse::Err(VmMemoryResponseError(e)) => {
                Err(ApiClientError::RequestFailed(e))
            }
            VmMemoryResponse::RegisterMemory { region_id, .. } => Ok(region_id),
            _other => Err(ApiClientError::UnexpectedResponse),
        }
    }
    #[cfg(any(target_os = "android", target_os = "linux"))]
    pub fn mmap_and_register_memory(
        &self,
        mapping_address: GuestAddress,
        shm: base::SharedMemory,
        file_mapping_info: Vec<crate::VmMemoryFileMapping>,
    ) -> Result<u32> {
        let num_file_mappings = file_mapping_info.len();
        let req = VmMemoryRequest::MmapAndRegisterMemory {
            shm,
            dest: VmMemoryDestination::GuestPhysicalAddress(mapping_address.0),
            num_file_mappings,
        };
        self.tube.send(&req).map_err(ApiClientError::Send)?;
        for m in file_mapping_info.chunks(base::unix::SCM_MAX_FD) {
            self.tube
                .send_with_max_fds(&m, m.len())
                .map_err(ApiClientError::SendFds)?;
        }
        match self.tube.recv().map_err(ApiClientError::Recv)? {
            VmMemoryResponse::RegisterMemory { slot, .. } => Ok(slot),
            VmMemoryResponse::Err(VmMemoryResponseError(e)) => {
                Err(ApiClientError::RequestFailed(e))
            }
            _ => Err(ApiClientError::UnexpectedResponse),
        }
    }
    pub fn dynamically_free_memory_ranges(&self, ranges: Vec<(GuestAddress, u64)>) -> Result<()> {
        self.request_unit(&VmMemoryRequest::DynamicallyFreeMemoryRanges { ranges })
    }
    pub fn dynamically_reclaim_memory_ranges(
        &self,
        ranges: Vec<(GuestAddress, u64)>,
    ) -> Result<()> {
        self.request_unit(&VmMemoryRequest::DynamicallyReclaimMemoryRanges { ranges })
    }
    pub fn unregister_memory(&self, region: VmMemoryRegionId) -> Result<()> {
        self.request_unit(&VmMemoryRequest::UnregisterMemory(region))
    }
    pub fn register_io_event(&self, event: Event, addr: u64, datamatch: Datamatch) -> Result<()> {
        self.request_unit(&VmMemoryRequest::IoEventRaw(IoEventUpdateRequest {
            event,
            addr,
            datamatch,
            register: true,
        }))
    }
    pub fn unregister_io_event(&self, event: Event, addr: u64, datamatch: Datamatch) -> Result<()> {
        self.request_unit(&VmMemoryRequest::IoEventRaw(IoEventUpdateRequest {
            event,
            addr,
            datamatch,
            register: false,
        }))
    }
    pub fn balloon_target_reached(&self, size: u64) -> Result<()> {
        self.request_unit(&VmMemoryRequest::BalloonTargetReached { size })
    }
}
impl AsRawDescriptor for VmMemoryClient {
    fn as_raw_descriptor(&self) -> RawDescriptor {
        self.tube.as_raw_descriptor()
    }
}