// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved.
//
// StratoVirt is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan
// PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//         http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

use super::Result;
use error_chain::bail;
use kvm_bindings::__IncompleteArrayField;

pub const IOCB_FLAG_RESFD: u32 = 1;
pub const IOCB_FLAG_IOPRIO: u32 = 1 << 1;

#[derive(Debug, Clone)]
pub struct Iovec {
    pub iov_base: u64,
    pub iov_len: u64,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Default)]
pub struct IoCb {
    pub data: u64,
    pub key: u32,
    pub aio_reserved1: u32,
    pub aio_lio_opcode: u16,
    pub aio_reqprio: u16,
    pub aio_fildes: u32,
    pub aio_buf: u64,
    pub aio_nbytes: u64,
    pub aio_offset: u64,
    pub aio_reserved2: u64,
    pub aio_flags: u32,
    pub aio_resfd: u32,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
pub enum IoCmd {
    Pread = 0,
    Pwrite = 1,
    Fsync = 2,
    Fdsync = 3,
    Noop = 6,
    Preadv = 7,
    Pwritev = 8,
}

#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Default)]
pub struct IoEvent {
    pub data: u64,
    pub obj: u64,
    pub res: i64,
    pub res2: i64,
}

#[allow(non_camel_case_types)]
pub enum IoContext {}

pub struct EventResult {
    pub events: Vec<IoEvent>,
    pub nr: usize,
}

pub struct LibaioContext {
    pub ctx: *mut IoContext,
    pub max_size: i32,
}

#[repr(C)]
#[derive(Default)]
pub struct AioRing {
    id: u32,
    nr: u32,
    head: u32,
    tail: u32,

    magic: u32,
    compat_features: u32,
    incompat_features: u32,
    header_length: u32,

    io_events: __IncompleteArrayField<IoEvent>,
}

impl LibaioContext {
    pub fn new(max_size: i32) -> Result<Self> {
        let mut ctx = std::ptr::null_mut();

        let ret = unsafe { libc::syscall(libc::SYS_io_setup, max_size, &mut ctx) };
        if ret < 0 {
            bail!("Failed to setup aio context, return {}.", ret);
        }

        Ok(LibaioContext { ctx, max_size })
    }

    pub fn submit(&self, nr: i64, iocbp: &mut [*mut IoCb]) -> Result<()> {
        let ret = unsafe { libc::syscall(libc::SYS_io_submit, self.ctx, nr, iocbp.as_ptr()) };
        if ret < 0 {
            bail!("Failed to submit aio, return {}.", ret);
        }

        Ok(())
    }

    pub fn get_events(&self) -> (&[IoEvent], u32, u32) {
        let ring = self.ctx as *mut AioRing;
        let head = unsafe { (*ring).head };
        let tail = unsafe { (*ring).tail };
        let ring_nr = unsafe { (*ring).nr };
        let nr = if tail >= head {
            tail - head
        } else {
            ring_nr - head
        };
        unsafe { (*ring).head = (head + nr) % ring_nr };

        let io_events: &[IoEvent] = unsafe { (*ring).io_events.as_slice(ring_nr as usize) };

        (io_events, head, head + nr)
    }
}