use std::convert::TryFrom;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use anyhow::Context;
use base::warn;
use serde::Deserialize;
use serde::Serialize;
use snapshot::AnySnapshot;
use crate::pci::CrosvmDeviceId;
use crate::BusAccessInfo;
use crate::BusDevice;
use crate::DeviceId;
use crate::IrqEdgeEvent;
use crate::Suspendable;
const RTCDR: u64 = 0x0;
const RTCMR: u64 = 0x4;
const RTCSTAT: u64 = 0x8;
const RTCEOI: u64 = 0x8;
const RTCLR: u64 = 0xC;
const RTCCR: u64 = 0x10;
pub const PL030_AMBA_IOMEM_SIZE: u64 = 0x1000;
const AMBA_ID_OFFSET: u64 = PL030_AMBA_IOMEM_SIZE - 0x20;
const AMBA_MASK_OFFSET: u64 = PL030_AMBA_IOMEM_SIZE - 0x28;
pub const PL030_AMBA_ID: u32 = 0x00041030;
pub const PL030_AMBA_MASK: u32 = 0x000FFFFF;
pub struct Pl030 {
    alarm_evt: IrqEdgeEvent,
    counter_delta_time: u32,
    match_value: u32,
    interrupt_active: bool,
}
#[derive(Debug, Serialize, Deserialize)]
struct Pl030Snapshot {
    counter_delta_time: u32,
    match_value: u32,
    interrupt_active: bool,
}
fn get_epoch_time() -> u32 {
    let epoch_time = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("SystemTime::duration_since failed");
    epoch_time.as_secs() as u32
}
impl Pl030 {
    pub fn new(evt: IrqEdgeEvent) -> Pl030 {
        Pl030 {
            alarm_evt: evt,
            counter_delta_time: get_epoch_time(),
            match_value: 0,
            interrupt_active: false,
        }
    }
}
impl BusDevice for Pl030 {
    fn device_id(&self) -> DeviceId {
        CrosvmDeviceId::Pl030.into()
    }
    fn debug_label(&self) -> String {
        "Pl030".to_owned()
    }
    fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
        let data_array = match <&[u8; 4]>::try_from(data) {
            Ok(array) => array,
            _ => {
                warn!("bad write size: {} for pl030", data.len());
                return;
            }
        };
        let reg_val = u32::from_ne_bytes(*data_array);
        match info.offset {
            RTCDR => {
                warn!("invalid write to read-only RTCDR register");
            }
            RTCMR => {
                self.match_value = reg_val;
                warn!("Not implemented: VM tried to set an RTC alarm");
            }
            RTCEOI => {
                if reg_val == 0 {
                    self.interrupt_active = false;
                } else {
                    self.alarm_evt.trigger().unwrap();
                    self.interrupt_active = true;
                }
            }
            RTCLR => {
                warn!("Not implemented: VM tried to set the RTC");
            }
            RTCCR => {
                self.counter_delta_time = get_epoch_time();
            }
            o => panic!("pl030: bad write {}", o),
        }
    }
    fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
        let data_array = match <&mut [u8; 4]>::try_from(data) {
            Ok(array) => array,
            _ => {
                warn!("bad write size for pl030");
                return;
            }
        };
        let reg_content: u32 = match info.offset {
            RTCDR => get_epoch_time(),
            RTCMR => self.match_value,
            RTCSTAT => self.interrupt_active as u32,
            RTCLR => {
                warn!("invalid read of RTCLR register");
                0
            }
            RTCCR => get_epoch_time() - self.counter_delta_time,
            AMBA_ID_OFFSET => PL030_AMBA_ID,
            AMBA_MASK_OFFSET => PL030_AMBA_MASK,
            o => panic!("pl030: bad read {}", o),
        };
        *data_array = reg_content.to_ne_bytes();
    }
}
impl Suspendable for Pl030 {
    fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
        AnySnapshot::to_any(Pl030Snapshot {
            counter_delta_time: self.counter_delta_time,
            match_value: self.match_value,
            interrupt_active: self.interrupt_active,
        })
        .with_context(|| format!("error serializing {}", self.debug_label()))
    }
    fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
        let deser: Pl030Snapshot = AnySnapshot::from_any(data)
            .with_context(|| format!("failed to deserialize {}", self.debug_label()))?;
        self.counter_delta_time = deser.counter_delta_time;
        self.match_value = deser.match_value;
        self.interrupt_active = deser.interrupt_active;
        Ok(())
    }
    fn sleep(&mut self) -> anyhow::Result<()> {
        Ok(())
    }
    fn wake(&mut self) -> anyhow::Result<()> {
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    const AARCH64_RTC_ADDR: u64 = 0x2000;
    fn pl030_bus_address(offset: u64) -> BusAccessInfo {
        BusAccessInfo {
            address: AARCH64_RTC_ADDR + offset,
            offset,
            id: 0,
        }
    }
    #[test]
    fn test_interrupt_status_register() {
        let event = IrqEdgeEvent::new().unwrap();
        let mut device = Pl030::new(event.try_clone().unwrap());
        let mut register = [0, 0, 0, 0];
        device.write(pl030_bus_address(RTCEOI), &[1, 0, 0, 0]);
        device.read(pl030_bus_address(RTCSTAT), &mut register);
        assert_eq!(register, [1, 0, 0, 0]);
        event.get_trigger().wait().unwrap();
        device.write(pl030_bus_address(RTCEOI), &[0, 0, 0, 0]);
        device.read(pl030_bus_address(RTCSTAT), &mut register);
        assert_eq!(register, [0, 0, 0, 0]);
    }
    #[test]
    fn test_match_register() {
        let mut device = Pl030::new(IrqEdgeEvent::new().unwrap());
        let mut register = [0, 0, 0, 0];
        device.write(pl030_bus_address(RTCMR), &[1, 2, 3, 4]);
        device.read(pl030_bus_address(RTCMR), &mut register);
        assert_eq!(register, [1, 2, 3, 4]);
    }
}