use std::collections::HashSet;
use std::fs;
use std::iter::repeat;
use std::path::PathBuf;
#[cfg(windows)]
use base::error;
use serde::Deserialize;
use serde::Serialize;
use serde_keyvalue::FromKeyValues;
use thiserror::Error as ThisError;
use crate::BusAccessInfo;
use crate::BusDevice;
use crate::DeviceId;
use crate::Suspendable;
pub const FW_CFG_BASE_PORT: u64 = 0x510;
pub const FW_CFG_WIDTH: u64 = 0x4;
pub const FW_CFG_MAX_FILE_SLOTS: usize = 16384 - FW_CFG_FILE_FIRST;
const FW_CFG_FILE_FIRST: usize = 0x0020;
const FW_CFG_SELECTOR_PORT_OFFSET: u64 = 0x0;
const FW_CFG_DATA_PORT_OFFSET: u64 = 0x1;
const FW_CFG_SELECTOR_RW_MASK: u16 = 0x2000;
const FW_CFG_SELECTOR_ARCH_MASK: u16 = 0x4000;
const FW_CFG_SELECTOR_SELECT_MASK: u16 = 0xbfff;
const FW_CFG_SIGNATURE: [u8; 4] = [b'Q', b'E', b'M', b'U'];
const FW_CFG_REVISION: [u8; 4] = [0, 0, 0, 1];
const FW_CFG_SIGNATURE_SELECTOR: u16 = 0x0000;
const FW_CFG_REVISION_SELECTOR: u16 = 0x0001;
const FW_CFG_FILE_DIR_SELECTOR: u16 = 0x0019;
const FW_CFG_FILENAME_SIZE: usize = 56;
#[derive(ThisError, Debug)]
pub enum Error {
    #[error("Ran out of file slots")]
    InsufficientFileSlots,
    #[error("File already exists")]
    FileAlreadyExists,
    #[error("Data blob's size too large: overflowed u32")]
    SizeOverflow,
    #[error("too many entries: oveflows u16 selector")]
    IndexOverflow,
    #[error("Filename must be less than 55 characters long")]
    FileNameTooLong,
    #[error("Unable to open file {0} for fw_cfg: {1}")]
    FileOpen(PathBuf, std::io::Error),
    #[error("fw_cfg parameters must have exactly one of string or path")]
    StringOrPathRequired,
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Debug, Deserialize, Serialize, FromKeyValues, PartialEq, Eq)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct FwCfgParameters {
    pub name: String,
    pub string: Option<String>,
    pub path: Option<PathBuf>,
}
#[derive(PartialEq)]
pub enum FwCfgItemType {
    GenericItem,
    ArchSpecificItem,
    FileDir,
    Signature,
    RevisionVector,
}
impl FwCfgItemType {
    fn value(&self) -> usize {
        match self {
            FwCfgItemType::ArchSpecificItem => 1,
            _ => 0,
        }
    }
}
struct FwCfgFile {
    pub size: u32,
    pub select: u16,
    pub name: String,
}
#[derive(Clone)]
struct FwCfgEntry {
    pub allow_write: bool,
    pub data: Vec<u8>,
}
pub struct FwCfgDevice {
    file_slots: usize,
    entries: [Vec<FwCfgEntry>; 2],
    files: Vec<FwCfgFile>,
    cur_item_type: FwCfgItemType,
    cur_entry: u16,
    cur_offset: usize,
    file_names: HashSet<String>,
}
impl FwCfgDevice {
    pub fn new(file_slots: usize, fw_cfg_parameters: Vec<FwCfgParameters>) -> Result<FwCfgDevice> {
        let mut device = FwCfgDevice {
            file_slots,
            entries: [
                vec![
                    FwCfgEntry {
                        allow_write: false,
                        data: vec![]
                    };
                    FW_CFG_FILE_FIRST
                ],
                Vec::new(),
            ],
            files: Vec::new(),
            cur_item_type: FwCfgItemType::GenericItem,
            cur_entry: 0,
            cur_offset: 0,
            file_names: HashSet::new(),
        };
        for param in fw_cfg_parameters {
            let data = match (¶m.string, ¶m.path) {
                (Some(string), None) => string.as_bytes().to_vec(),
                (None, Some(path)) => {
                    fs::read(path).map_err(|e| Error::FileOpen(path.clone(), e))?
                }
                _ => return Err(Error::StringOrPathRequired),
            };
            device.add_file(¶m.name, data, FwCfgItemType::GenericItem)?
        }
        device.add_bytes(FW_CFG_SIGNATURE.to_vec(), FwCfgItemType::Signature);
        device.add_bytes(FW_CFG_REVISION.to_vec(), FwCfgItemType::RevisionVector);
        Ok(device)
    }
    pub fn add_file(
        &mut self,
        filename: &str,
        data: Vec<u8>,
        item_type: FwCfgItemType,
    ) -> Result<()> {
        if self.files.len() >= FW_CFG_MAX_FILE_SLOTS || self.files.len() >= self.file_slots {
            return Err(Error::InsufficientFileSlots);
        }
        if filename.len() > FW_CFG_FILENAME_SIZE - 1 {
            return Err(Error::FileNameTooLong);
        }
        let index = self.entries[item_type.value()].len();
        if self.file_names.contains(filename) {
            return Err(Error::FileAlreadyExists);
        }
        let size: u32 = data.len().try_into().map_err(|_| Error::SizeOverflow)?;
        let mut select: u16 = (index).try_into().map_err(|_| Error::IndexOverflow)?;
        if item_type == FwCfgItemType::ArchSpecificItem {
            select |= FW_CFG_SELECTOR_ARCH_MASK;
        }
        let new_file = FwCfgFile {
            size,
            select,
            name: filename.to_owned(),
        };
        self.add_bytes(data, item_type);
        self.files.push(new_file);
        self.file_names.insert(filename.to_string());
        self.update_file_dir_entry();
        Ok(())
    }
    fn add_bytes(&mut self, data: Vec<u8>, item_type: FwCfgItemType) {
        let new_entry = FwCfgEntry {
            allow_write: false,
            data,
        };
        match item_type {
            FwCfgItemType::GenericItem | FwCfgItemType::ArchSpecificItem => {
                self.entries[item_type.value()].push(new_entry)
            }
            FwCfgItemType::FileDir => {
                self.entries[item_type.value()][FW_CFG_FILE_DIR_SELECTOR as usize] = new_entry
            }
            FwCfgItemType::Signature => {
                self.entries[item_type.value()][FW_CFG_SIGNATURE_SELECTOR as usize] = new_entry
            }
            FwCfgItemType::RevisionVector => {
                self.entries[item_type.value()][FW_CFG_REVISION_SELECTOR as usize] = new_entry
            }
        }
    }
    fn update_file_dir_entry(&mut self) {
        let mut raw_file_dir: Vec<u8> = Vec::new();
        let files_dir_count = self.files.len() as u32;
        raw_file_dir.extend_from_slice(&files_dir_count.to_be_bytes());
        for file in &self.files {
            raw_file_dir.extend_from_slice(&file.size.to_be_bytes());
            raw_file_dir.extend_from_slice(&file.select.to_be_bytes());
            raw_file_dir.extend_from_slice(&[0, 0]);
            raw_file_dir.extend_from_slice(file.name.as_bytes());
            raw_file_dir.extend(repeat(0).take(FW_CFG_FILENAME_SIZE - file.name.len()));
        }
        self.add_bytes(raw_file_dir, FwCfgItemType::FileDir);
    }
}
impl BusDevice for FwCfgDevice {
    fn device_id(&self) -> DeviceId {
        super::CrosvmDeviceId::FwCfg.into()
    }
    fn debug_label(&self) -> String {
        "FwCfg".to_owned()
    }
    fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
        if data.len() != 1 {
            return;
        }
        if info.offset == FW_CFG_DATA_PORT_OFFSET {
            let entries_index = self.cur_entry as usize;
            if self.cur_offset
                >= self.entries[self.cur_item_type.value()][entries_index]
                    .data
                    .len()
            {
                data[0] = 0x00;
                return;
            }
            data[0] = self.entries[self.cur_item_type.value()][entries_index].data[self.cur_offset];
            self.cur_offset += 1;
        }
    }
    fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
        if info.offset == FW_CFG_SELECTOR_PORT_OFFSET {
            if data.len() != 2 {
                return;
            }
            let Ok(selector) = data.try_into().map(u16::from_le_bytes) else {
                return;
            };
            self.cur_offset = 0;
            match selector {
                FW_CFG_FILE_DIR_SELECTOR => {
                    self.cur_entry = FW_CFG_FILE_DIR_SELECTOR;
                }
                FW_CFG_REVISION_SELECTOR => {
                    self.cur_entry = FW_CFG_REVISION_SELECTOR;
                }
                FW_CFG_SIGNATURE_SELECTOR => {
                    self.cur_entry = FW_CFG_SIGNATURE_SELECTOR;
                }
                _ => {
                    let entries_index = selector as usize;
                    if (FW_CFG_SELECTOR_ARCH_MASK & selector) > 0 {
                        self.cur_item_type = FwCfgItemType::ArchSpecificItem;
                    } else {
                        self.cur_item_type = FwCfgItemType::GenericItem;
                    }
                    if self.entries[self.cur_item_type.value()].len() <= entries_index {
                        return;
                    }
                    self.entries[self.cur_item_type.value()][entries_index].allow_write =
                        (FW_CFG_SELECTOR_RW_MASK & selector) > 0;
                    if (FW_CFG_SELECTOR_ARCH_MASK & selector) > 0 {
                        self.cur_item_type = FwCfgItemType::ArchSpecificItem;
                    } else {
                        self.cur_item_type = FwCfgItemType::GenericItem;
                    }
                    self.cur_entry = selector & FW_CFG_SELECTOR_SELECT_MASK;
                }
            }
        }
    }
}
impl Suspendable for FwCfgDevice {
    fn sleep(&mut self) -> anyhow::Result<()> {
        Ok(())
    }
    fn wake(&mut self) -> anyhow::Result<()> {
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use serde_keyvalue::*;
    use super::*;
    use crate::FW_CFG_BASE_PORT;
    const MAGIC_BYTE: u8 = 111;
    const MAGIC_BYTE_ALT: u8 = 222;
    const FILENAME: &str = "/test/device/crosvmval";
    const FILENAMES: [&str; 6] = [
        "test/hello.txt",
        "user/data/mydata.txt",
        "bruschetta/user/foo",
        "valid_unix/me/home/dir/back.txt",
        "/dev/null",
        "google/unix/sys/maple.txt",
    ];
    fn default_params() -> Vec<FwCfgParameters> {
        Vec::new()
    }
    fn get_contents() -> [Vec<u8>; 6] {
        [
            b"CROSVM".to_vec(),
            b"GOOGLE".to_vec(),
            b"FWCONFIG".to_vec(),
            b"PIZZA".to_vec(),
            b"CHROMEOS".to_vec(),
            b"42".to_vec(),
        ]
    }
    fn make_device(
        filenames: &[&str],
        contents: &[Vec<u8>],
        params: &[FwCfgParameters],
        file_slots: &usize,
    ) -> Result<FwCfgDevice> {
        let mut device = FwCfgDevice::new(*file_slots, params.to_owned())?;
        let count = filenames.len();
        for i in 0..count {
            device.add_file(
                filenames[i],
                contents[i].clone(),
                FwCfgItemType::GenericItem,
            )?;
        }
        Ok(device)
    }
    fn from_fw_cfg_arg(options: &str) -> std::result::Result<FwCfgParameters, ParseError> {
        from_key_values(options)
    }
    fn read_u32(device: &mut FwCfgDevice, bai: BusAccessInfo, data: &mut [u8]) -> u32 {
        let mut bytes: [u8; 4] = [0, 0, 0, 0];
        device.read(bai, data);
        bytes[0] = data[0];
        device.read(bai, data);
        bytes[1] = data[0];
        device.read(bai, data);
        bytes[2] = data[0];
        device.read(bai, data);
        bytes[3] = data[0];
        u32::from_be_bytes(bytes)
    }
    fn read_u16(device: &mut FwCfgDevice, bai: BusAccessInfo, data: &mut [u8]) -> u16 {
        let mut bytes: [u8; 2] = [0, 0];
        device.read(bai, data);
        bytes[0] = data[0];
        device.read(bai, data);
        bytes[1] = data[0];
        u16::from_be_bytes(bytes)
    }
    fn read_u8(device: &mut FwCfgDevice, bai: BusAccessInfo, data: &mut [u8]) -> u8 {
        let mut bytes: [u8; 1] = [0];
        device.read(bai, data);
        bytes[0] = data[0];
        u8::from_be_bytes(bytes)
    }
    fn read_char_56(device: &mut FwCfgDevice, bai: BusAccessInfo, data: &mut [u8]) -> String {
        let mut c: char = read_u8(device, bai, data) as char;
        let mut count = 1; let mut name: String = String::new();
        while c != '\0' {
            name.push(c);
            c = read_u8(device, bai, data) as char;
            count += 1;
        }
        while count < FW_CFG_FILENAME_SIZE {
            read_u8(device, bai, data);
            count += 1;
        }
        name
    }
    fn get_entry(
        device: &mut FwCfgDevice,
        mut bai: BusAccessInfo,
        size: usize,
        selector: u16,
    ) -> Vec<u8> {
        let mut data: Vec<u8> = vec![0];
        let mut blob: Vec<u8> = Vec::new();
        bai.address = FW_CFG_BASE_PORT;
        bai.offset = FW_CFG_SELECTOR_PORT_OFFSET;
        let selector: [u8; 2] = selector.to_le_bytes();
        device.write(bai, &selector);
        bai.offset = FW_CFG_DATA_PORT_OFFSET;
        for _i in 0..size {
            read_u8(device, bai, &mut data[..]);
            blob.push(data[0]);
        }
        blob
    }
    fn assert_read_entries(filenames: &[&str], device: &mut FwCfgDevice, bai: BusAccessInfo) {
        let data_len = device.entries[0][0 + FW_CFG_FILE_FIRST].data.len();
        assert_eq!(
            get_entry(device, bai, data_len, (0 + FW_CFG_FILE_FIRST) as u16),
            device.entries[0][0 + FW_CFG_FILE_FIRST].data
        );
        for i in (FW_CFG_FILE_FIRST + 1)..filenames.len() {
            let data_len = device.entries[0][i].data.len();
            assert_eq!(
                get_entry(device, bai, data_len, (i + FW_CFG_FILE_FIRST) as u16),
                device.entries[0][i].data
            );
        }
    }
    fn assert_read_file_dir(
        filenames: &[&str],
        contents: &[Vec<u8>],
        device: &mut FwCfgDevice,
        bai: BusAccessInfo,
    ) {
        let mut data: Vec<u8> = vec![0];
        let file_count = read_u32(device, bai, &mut data[..]);
        assert_eq!(file_count, filenames.len() as u32);
        for i in 0..filenames.len() {
            let file_size = read_u32(device, bai, &mut data[..]);
            assert_eq!(file_size, contents[i].len() as u32);
            let file_select = read_u16(device, bai, &mut data[..]);
            assert_eq!(file_select - (FW_CFG_FILE_FIRST as u16), i as u16);
            let file_reserved = read_u16(device, bai, &mut data[..]);
            assert_eq!(file_reserved, 0);
            let file_name = read_char_56(device, bai, &mut data[..]);
            assert_eq!(file_name, FILENAMES[i]);
        }
    }
    fn setup_read(
        filenames: &[&str],
        contents: &[Vec<u8>],
        selector: u16,
    ) -> (FwCfgDevice, BusAccessInfo) {
        let mut device = make_device(
            filenames,
            contents,
            &default_params(),
            &(filenames.len() + 5),
        )
        .unwrap();
        let mut bai = BusAccessInfo {
            offset: FW_CFG_SELECTOR_PORT_OFFSET,
            address: FW_CFG_BASE_PORT,
            id: 0,
        };
        let selector: [u8; 2] = selector.to_le_bytes();
        device.write(bai, &selector);
        bai.offset = FW_CFG_DATA_PORT_OFFSET;
        (device, bai)
    }
    #[test]
    fn params_from_key_values() {
        from_fw_cfg_arg("").expect_err("parsing empty string should fail");
        let params = from_fw_cfg_arg("name=foo,path=/path/to/input").unwrap();
        assert_eq!(
            params,
            FwCfgParameters {
                name: "foo".into(),
                path: Some("/path/to/input".into()),
                string: None,
            }
        );
        let params = from_fw_cfg_arg("name=bar,string=testdata").unwrap();
        assert_eq!(
            params,
            FwCfgParameters {
                name: "bar".into(),
                string: Some("testdata".into()),
                path: None,
            }
        );
    }
    #[test]
    fn attempt_underflow_read() {
        let (_device, _bai) = setup_read(
            &FILENAMES,
            &get_contents(),
            (FW_CFG_FILE_FIRST - 0x05) as u16,
        );
    }
    #[test]
    fn write_one_byte_file() {
        let mut fw_cfg = FwCfgDevice::new(100, default_params()).unwrap();
        let data = vec![MAGIC_BYTE];
        fw_cfg
            .add_file(FILENAME, data, FwCfgItemType::GenericItem)
            .expect("File insert failed");
        let ind = fw_cfg.entries[0].len();
        assert_eq!(
            ind,
            FW_CFG_FILE_FIRST + 1,
            "Insertion into fw_cfg failed: Index is wrong. expected {}, got {},
                 ",
            FW_CFG_FILE_FIRST + 1,
            ind
        );
        assert_eq!(
            fw_cfg.entries[0][ind - 1].data,
            vec![MAGIC_BYTE],
            "Insertion failed: unexpected fw_cfg entry values"
        );
    }
    #[test]
    fn write_four_byte_file() {
        let mut fw_cfg = FwCfgDevice::new(100, default_params()).unwrap();
        let data = vec![MAGIC_BYTE, MAGIC_BYTE_ALT, MAGIC_BYTE, MAGIC_BYTE_ALT];
        fw_cfg
            .add_file(FILENAME, data, FwCfgItemType::GenericItem)
            .expect("File insert failed");
        let ind = fw_cfg.entries[0].len();
        assert_eq!(
            ind,
            FW_CFG_FILE_FIRST + 1,
            "Insertion into fw_cfg failed: Index is wrong. expected {}, got {}",
            FW_CFG_FILE_FIRST + 1,
            ind
        );
        assert_eq!(
            fw_cfg.entries[0][ind - 1].data,
            vec![MAGIC_BYTE, MAGIC_BYTE_ALT, MAGIC_BYTE, MAGIC_BYTE_ALT],
            "Insertion failed: unexpected fw_cfg entry values"
        );
    }
    #[test]
    #[should_panic]
    fn write_file_one_slot_expect_nop() {
        let mut fw_cfg = FwCfgDevice::new(0, default_params()).unwrap();
        let data = vec![MAGIC_BYTE];
        fw_cfg
            .add_file(FILENAME, data, FwCfgItemType::GenericItem)
            .expect("File insert failed");
    }
    #[test]
    #[should_panic]
    fn write_two_files_no_slots_expect_nop_on_second() {
        let mut fw_cfg = FwCfgDevice::new(1, default_params()).unwrap();
        let data = vec![MAGIC_BYTE];
        let data2 = vec![MAGIC_BYTE_ALT];
        fw_cfg
            .add_file(FILENAME, data, FwCfgItemType::GenericItem)
            .expect("File insert failed");
        assert_eq!(
            fw_cfg.entries[0].len(),
            1,
            "Insertion into fw_cfg failed: Expected {} elements, got {}",
            1,
            fw_cfg.entries[0].len()
        );
        fw_cfg
            .add_file(FILENAME, data2, FwCfgItemType::GenericItem)
            .expect("File insert failed");
    }
    #[test]
    fn read_fw_cfg_signature() {
        let mut data: Vec<u8> = vec![0];
        let (mut device, bai) = setup_read(&FILENAMES, &get_contents(), FW_CFG_SIGNATURE_SELECTOR);
        let signature = read_u32(&mut device, bai, &mut data[..]).to_be_bytes();
        assert_eq!(signature, FW_CFG_SIGNATURE);
    }
    #[test]
    fn read_fw_cfg_revision() {
        let mut data: Vec<u8> = vec![0];
        let (mut device, bai) = setup_read(&FILENAMES, &get_contents(), FW_CFG_REVISION_SELECTOR);
        let revision = read_u32(&mut device, bai, &mut data[..]).to_be_bytes();
        assert_eq!(revision, FW_CFG_REVISION);
    }
    #[test]
    fn read_file_dir() {
        let contents = get_contents();
        let (mut device, bai) = setup_read(&FILENAMES, &contents, FW_CFG_FILE_DIR_SELECTOR);
        assert_read_file_dir(&FILENAMES, &contents, &mut device, bai);
    }
    #[test]
    fn read_fw_cfg_entries() {
        let contents = get_contents();
        let (mut device, bai) = setup_read(&FILENAMES, &contents, (0 + FW_CFG_FILE_FIRST) as u16);
        assert_read_entries(&FILENAMES, &mut device, bai);
    }
    #[test]
    fn read_whole_device() {
        let contents = get_contents();
        let mut data: Vec<u8> = vec![0];
        let (mut device, mut bai) = setup_read(&FILENAMES, &contents, FW_CFG_REVISION_SELECTOR);
        let revision = read_u32(&mut device, bai, &mut data[..]).to_be_bytes();
        assert_eq!(revision, FW_CFG_REVISION);
        let i = FILENAMES.len() - 1;
        let data_len = device.entries[0][i].data.len();
        assert_eq!(
            get_entry(&mut device, bai, data_len, (i + FW_CFG_FILE_FIRST) as u16),
            device.entries[0][i].data
        );
        bai.address = FW_CFG_BASE_PORT;
        bai.offset = FW_CFG_SELECTOR_PORT_OFFSET;
        device.write(bai, &FW_CFG_FILE_DIR_SELECTOR.to_le_bytes());
        bai.offset = FW_CFG_DATA_PORT_OFFSET;
        assert_read_file_dir(&FILENAMES, &contents, &mut device, bai);
        let data_len = device.entries[0][FW_CFG_FILE_FIRST + 0].data.len();
        assert_eq!(
            get_entry(&mut device, bai, data_len, (0 + FW_CFG_FILE_FIRST) as u16),
            device.entries[0][FW_CFG_FILE_FIRST + 0].data
        );
    }
    #[test]
    fn read_incorrect_bytes() {
        let contents = get_contents();
        let mut data: Vec<u8> = vec![0];
        let (mut device, mut bai) =
            setup_read(&FILENAMES, &contents, (0 + FW_CFG_FILE_FIRST) as u16);
        for _i in 1..1000 {
            let _random_bytes = read_u32(&mut device, bai, &mut data[..]);
        }
        bai.address = FW_CFG_BASE_PORT;
        device.write(bai, &FW_CFG_FILE_DIR_SELECTOR.to_le_bytes());
        bai.offset = FW_CFG_DATA_PORT_OFFSET;
        for _i in 1..10000 {
            let mut data: Vec<u8> = vec![0];
            let _random_bytes = read_u32(&mut device, bai, &mut data[..]);
        }
        bai.address = FW_CFG_BASE_PORT;
        bai.offset = FW_CFG_SELECTOR_PORT_OFFSET;
        device.write(bai, &FW_CFG_FILE_DIR_SELECTOR.to_le_bytes());
        bai.offset = FW_CFG_DATA_PORT_OFFSET;
        assert_read_file_dir(&FILENAMES, &contents, &mut device, bai);
        let i = FILENAMES.len() - 1;
        bai.address = FW_CFG_BASE_PORT;
        bai.offset = FW_CFG_SELECTOR_PORT_OFFSET;
        device.write(bai, &(FW_CFG_FILE_FIRST + i).to_le_bytes());
        bai.offset = FW_CFG_DATA_PORT_OFFSET;
        for _i in 1..1000 {
            let _random_bytes = read_u32(&mut device, bai, &mut data[..]);
        }
        assert_read_entries(&FILENAMES, &mut device, bai);
    }
}