// Copyright (c) 2022 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 std::collections::{HashMap, LinkedList};
use std::sync::{Arc, Mutex};

use crate::usb::{UsbDeviceOps, UsbPort};
use anyhow::{bail, Result};

/// The key is bus name, the value is the device which can attach other devices.
pub type BusDeviceMap = Arc<Mutex<HashMap<String, Arc<Mutex<dyn BusDeviceOps>>>>>;

/// USB bus used to manage USB ports.
#[derive(Default)]
pub struct UsbBus {
    free_ports: LinkedList<Arc<Mutex<UsbPort>>>,
    used_ports: LinkedList<Arc<Mutex<UsbPort>>>,
}

impl UsbBus {
    pub fn new() -> Self {
        UsbBus {
            free_ports: LinkedList::new(),
            used_ports: LinkedList::new(),
        }
    }

    /// Register USB port to the bus.
    pub fn register_usb_port(&mut self, port: &Arc<Mutex<UsbPort>>) {
        let mut locked_port = port.lock().unwrap();
        locked_port.path = format!("{}", locked_port.index + 1);
        self.free_ports.push_back(port.clone());
    }

    /// Assign USB port and attach the device.
    pub fn assign_usb_port(
        &mut self,
        dev: &Arc<Mutex<dyn UsbDeviceOps>>,
    ) -> Result<Arc<Mutex<UsbPort>>> {
        if let Some(port) = self.free_ports.pop_front() {
            let mut locked_dev = dev.lock().unwrap();
            locked_dev.set_usb_port(Some(Arc::downgrade(&port)));
            let mut locked_port = port.lock().unwrap();
            locked_port.dev = Some(dev.clone());
            drop(locked_port);
            self.used_ports.push_back(port.clone());
            Ok(port)
        } else {
            bail!("No available usb port");
        }
    }

    /// Find USB port by path.
    pub fn find_usb_port(&self, path: String) -> Option<Arc<Mutex<UsbPort>>> {
        for usb in &self.used_ports {
            if usb.lock().unwrap().path == path {
                return Some(usb.clone());
            }
        }
        None
    }
}

/// Bus device ops for USB controller to handle USB device attach/detach.
pub trait BusDeviceOps: Send + Sync {
    fn attach_device(&mut self, dev: &Arc<Mutex<dyn UsbDeviceOps>>) -> Result<()>;

    fn detach_device(&mut self, dev: &Arc<Mutex<dyn UsbDeviceOps>>) -> Result<()>;
}