use crate::hypercall::{HcCmd, HyperCall};
use std::io::{self, Read, Write};
use std::mem::size_of;

use parking_lot::{Mutex, MutexGuard};

/// Write a buffer or string across the hypervisor channel
///
/// Channel format:
///
/// ```text
/// [     4 bytes     ] [ (len - 4) bytes ]
///    channel_id             data
/// (u32 little endian)    untyped bytes
/// ```
pub(crate) fn write(channel_id: u32, output: impl AsRef<[u8]>) {
    HyperCall::from_buf(HcCmd::Write, channel_id, output.as_ref()).call();
}

/// Read into a buffer across the hypervisor channel
///
/// Channel format:
///
/// ```text
/// [          max(4, len) bytes         ] <-- data (untyped bytes)
/// [   4 bytes    ] <-- channel_id (u32 little endian)
/// ```
///
/// Note: the channel_id is set to be overwritten by the hypervisor.
pub(crate) fn read(channel_id: u32, output: &mut [u8]) -> usize {
    HyperCall::from_mut_buf(HcCmd::Read, channel_id, output).call()
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct PluginNotFound;

// Manager channel must be wrapped in a mutex to ensure that the channel isn't
// accessed between request and response.
lazy_static::lazy_static! {
    static ref MANAGER_CHANNEL: Mutex<RawChannel> = Mutex::new(RawChannel(HyperCall::new(HcCmd::GetManager).call() as u32));
}

/// Locks the manager channel for reading/writing
fn get_raw_manager_channel<'a>() -> MutexGuard<'a, RawChannel> {
    println!("got here at get_raw_manager_channel");
    MANAGER_CHANNEL.lock()
}

#[allow(dead_code)]
enum ManagerOp {
    GetChannelFromName = 0,
    DebugOutput = 2,
}

pub fn debug_output(out: &str) {
    let mut manager = get_raw_manager_channel();
    do_manager_operation(&mut manager, ManagerOp::DebugOutput, out.as_bytes());
}

/// Creates and sends a manager channel request. Parsing the response is left up to the caller.
///
/// ```text
/// [     4 bytes     ] [ payload.len() bytes ]
///    operation id            payload
/// (u32 little endian)      untyped bytes
/// ```
fn do_manager_operation(channel: &mut RawChannel, op: ManagerOp, payload: &[u8]) {
    let mut buf = Vec::with_capacity(payload.len() + size_of::<u32>());
    buf.extend_from_slice(&(op as u32).to_le_bytes());
    buf.extend_from_slice(payload);
    channel.write_packet(&buf);
}

/// Get the main channel for a given plugin name
///
/// ```text
///  Guest    |   Hypervisor
///           |
///  plugin --w->
///   name    |
///           |
///         <-r-- channel id
///           |
/// ```

pub fn get_main_raw_channel(plugin: &str) -> Result<RawChannel, PluginNotFound> {
    println!("called get_main_raw_channel in {}", plugin);
    let chval = HyperCall::from_string(HcCmd::GetChannelByName, 0, plugin).call();
    if chval != (-1 as isize) as usize {
        Ok(RawChannel(chval as u32))
    } else {
        Err(PluginNotFound)
    }

    // let mut manager =dbg!( get_raw_manager_channel());
    // let plugin = plugin.as_bytes();
    // println!("about to do manager operation");
    // do_manager_operation(&mut manager, ManagerOp::GetChannelFromName, plugin);

    // let mut channel_id_out = [0u8; 4];
    // manager.read_packet(&mut channel_id_out);

    // if channel_id_out != [0xff, 0xff, 0xff, 0xff] {
    //     let c = u32::from_le_bytes(channel_id_out);
    //     println!("Found that the channel returned is {}", c);
    //     Ok(RawChannel(c))
    // } else {
    //     Err(PluginNotFound)
    // }
}

/// Get the main channel for a given plugin name
///
/// ```text
///  Guest    |   Hypervisor
///           |
/// get new --w->
///  channel  |
///           |
///         <-r-- channel id
///           |
/// ```
pub fn get_new_channel() -> Result<RawChannel, PluginNotFound> {
    todo!()
    //let mut manager = get_raw_manager_channel();

    //// do_manager_operation(&mut manager, ManagerOp::GetNewChannel, &[]);

    //let mut channel_id_out = [0u8; 4];
    //manager.read_packet(&mut channel_id_out);

    //if channel_id_out != [0xff, 0xff, 0xff, 0xff] {
    //    Ok(RawChannel(u32::from_le_bytes(channel_id_out)))
    //} else {
    //    Err(PluginNotFound)
    //}
}

#[repr(transparent)]
#[derive(Debug)]
pub struct RawChannel(pub(crate) u32);

impl RawChannel {
    pub fn read_packet(&self, buf: &mut [u8]) -> usize {
        read(self.0, buf)
    }

    pub fn write_packet(&self, x: &[u8]) {
        write(self.0, x)
    }

    pub fn clone(&self) -> Self {
        Self(self.0)
    }
}

//const WRITE_BUF_SIZE: usize = 0x400;

//pub struct Channel {
//    inner: RawChannel,
//    write_buf: Box<[u8; WRITE_BUF_SIZE]>,
//    pos: usize,
//}

/// An std::io::{Read, Write} adapter for hyperchannels. Note: does not make any guarantees
/// about writes not being split across packets.
pub struct Channel {
    inner: RawChannel,
}

impl Channel {
    pub fn from_raw(channel: RawChannel) -> Self {
        Self {
            inner: channel,
            //write_buf: Box::new([0; WRITE_BUF_SIZE]),
            //pos: 0,
        }
    }

    pub fn into_raw(self) -> RawChannel {
        self.inner
    }

    pub fn clone(&self) -> Self {
        Self::from_raw(self.inner.clone())
    }

    pub fn main(name: &str) -> Result<Self, PluginNotFound> {
        let channel = get_main_raw_channel(name)?;

        Ok(Self::from_raw(channel))
    }
}

impl Read for Channel {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        //match  {
        //    0 => Err(io::Error::new(
        //        io::ErrorKind::Interrupted,
        //        "no bytes recieved",
        //    )),
        //    n => Ok(n),
        //}
        Ok(self.inner.read_packet(buf))
    }
}

impl Write for Channel {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.inner.write_packet(buf);

        Ok(buf.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

//impl Write for Channel {
//    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
//        let mut buf_pos = 0;
//        while (buf.len() - buf_pos) + self.pos > WRITE_BUF_SIZE {
//            let amount_to_buffer = WRITE_BUF_SIZE - self.pos;
//            self.write_buf[self.pos..].copy_from_slice(&buf[buf_pos..buf_pos + amount_to_buffer]);
//            buf_pos += amount_to_buffer;
//            self.flush()?;
//        }
//
//        let amount_to_write = buf.len() - buf_pos;
//        self.write_buf[self.pos..self.pos + amount_to_write].copy_from_slice(&buf[buf_pos..]);
//
//        Ok(buf.len())
//    }
//
//    fn flush(&mut self) -> std::io::Result<()> {
//        self.pos = 0;
//        self.inner.write_packet(&self.write_buf[..self.pos]);
//
//        Ok(())
//    }
//}
