use std::cmp::min;
use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
use anyhow::anyhow;
use anyhow::Context;
use base::custom_serde::deserialize_seq_to_arr;
use base::custom_serde::serialize_arr;
use base::error;
use base::info;
use base::Event;
use base::EventToken;
use base::Timer;
use base::TimerTrait;
use base::Tube;
use base::WaitContext;
use base::WorkerThread;
use chrono::DateTime;
use chrono::Datelike;
use chrono::TimeZone;
use chrono::Timelike;
use chrono::Utc;
use metrics::log_metric;
use metrics::MetricEventType;
use serde::Deserialize;
use serde::Serialize;
use snapshot::AnySnapshot;
use sync::Mutex;
use vm_control::VmResponse;
use crate::pci::CrosvmDeviceId;
use crate::BusAccessInfo;
use crate::BusDevice;
use crate::DeviceId;
use crate::IrqEdgeEvent;
use crate::Suspendable;
pub const RTC_IRQ: u8 = 8;
const INDEX_MASK: u8 = 0x7f;
const INDEX_OFFSET: u64 = 0x0;
const DATA_OFFSET: u64 = 0x1;
const DATA_LEN: usize = 128;
const RTC_REG_SEC: u8 = 0x0;
const RTC_REG_ALARM_SEC: u8 = 0x1;
const RTC_REG_MIN: u8 = 0x2;
const RTC_REG_ALARM_MIN: u8 = 0x3;
const RTC_REG_HOUR: u8 = 0x4;
const RTC_REG_ALARM_HOUR: u8 = 0x5;
const RTC_REG_WEEK_DAY: u8 = 0x6;
const RTC_REG_DAY: u8 = 0x7;
const RTC_REG_MONTH: u8 = 0x8;
const RTC_REG_YEAR: u8 = 0x9;
pub const RTC_REG_CENTURY: u8 = 0x32;
pub const RTC_REG_ALARM_DAY: u8 = 0x33;
pub const RTC_REG_ALARM_MONTH: u8 = 0x34;
const RTC_REG_B: u8 = 0x0b;
const RTC_REG_B_UNSUPPORTED: u8 = 0xdd;
const RTC_REG_B_24_HOUR_MODE: u8 = 0x02;
const RTC_REG_B_ALARM_ENABLE: u8 = 0x20;
const RTC_REG_C: u8 = 0x0c;
const RTC_REG_C_IRQF: u8 = 0x80;
const RTC_REG_C_AF: u8 = 0x20;
const RTC_REG_D: u8 = 0x0d;
const RTC_REG_D_VRT: u8 = 0x80; pub type CmosNowFn = fn() -> DateTime<Utc>;
struct AlarmState {
    alarm: Timer,
    vm_control: Tube,
    irq: IrqEdgeEvent,
    armed_time: Instant,
    clear_evt: Option<Event>,
}
impl AlarmState {
    fn trigger_rtc_interrupt(&self) -> anyhow::Result<Event> {
        self.irq.trigger().context("failed to trigger irq")?;
        let elapsed = self.armed_time.elapsed().as_millis();
        log_metric(
            MetricEventType::RtcWakeup,
            elapsed.try_into().unwrap_or(i64::MAX),
        );
        let msg = vm_control::VmRequest::Rtc {
            clear_evt: Event::new().context("failed to create clear event")?,
        };
        self.vm_control.send(&msg).context("send failed")?;
        let vm_control::VmRequest::Rtc { clear_evt } = msg else {
            unreachable!("message type failure");
        };
        match self.vm_control.recv().context("recv failed")? {
            VmResponse::Ok => Ok(clear_evt),
            resp => Err(anyhow!("unexpected rtc response: {:?}", resp)),
        }
    }
}
#[derive(Serialize)]
pub struct Cmos {
    index: u8,
    #[serde(serialize_with = "serialize_arr")]
    data: [u8; DATA_LEN],
    #[serde(skip_serializing)] now_fn: CmosNowFn,
    #[serde(skip_serializing)]
    alarm_time: Option<DateTime<Utc>>,
    #[serde(skip_serializing)]
    alarm_state: Arc<Mutex<AlarmState>>,
    #[serde(skip_serializing)] worker: Option<WorkerThread<()>>,
}
impl Cmos {
    pub fn new(
        mem_below_4g: u64,
        mem_above_4g: u64,
        now_fn: CmosNowFn,
        vm_control: Tube,
        irq: IrqEdgeEvent,
    ) -> anyhow::Result<Cmos> {
        let mut data = [0u8; DATA_LEN];
        data[0x0B] = RTC_REG_B_24_HOUR_MODE; let ext_mem = min(
            0xFFFF,
            mem_below_4g.saturating_sub(16 * 1024 * 1024) / (64 * 1024),
        );
        data[0x34] = ext_mem as u8;
        data[0x35] = (ext_mem >> 8) as u8;
        let high_mem = min(0xFFFFFF, mem_above_4g / (64 * 1024));
        data[0x5b] = high_mem as u8;
        data[0x5c] = (high_mem >> 8) as u8;
        data[0x5d] = (high_mem >> 16) as u8;
        Ok(Cmos {
            index: 0,
            data,
            now_fn,
            alarm_time: None,
            alarm_state: Arc::new(Mutex::new(AlarmState {
                alarm: Timer::new().context("cmos timer")?,
                irq,
                vm_control,
                armed_time: Instant::now(),
                clear_evt: None,
            })),
            worker: None,
        })
    }
    fn spawn_worker(&mut self, alarm_state: Arc<Mutex<AlarmState>>) {
        self.worker = Some(WorkerThread::start("CMOS_alarm", move |kill_evt| {
            if let Err(e) = run_cmos_worker(alarm_state, kill_evt) {
                error!("Failed to spawn worker {:?}", e);
            }
        }));
    }
    fn set_alarm(&mut self) {
        let mut state = self.alarm_state.lock();
        if self.data[RTC_REG_B as usize] & RTC_REG_B_ALARM_ENABLE != 0 {
            let now = (self.now_fn)();
            let target = alarm_from_registers(now.year(), &self.data).and_then(|this_year| {
                if this_year < now {
                    alarm_from_registers(now.year() + 1, &self.data)
                } else {
                    Some(this_year)
                }
            });
            if let Some(target) = target {
                if Some(target) != self.alarm_time {
                    self.alarm_time = Some(target);
                    state.armed_time = Instant::now();
                    let duration = target
                        .signed_duration_since(now)
                        .to_std()
                        .unwrap_or(Duration::new(0, 0));
                    if let Err(e) = state.alarm.reset_oneshot(duration) {
                        error!("Failed to set alarm {:?}", e);
                    }
                }
            }
        } else if self.alarm_time.take().is_some() {
            if let Err(e) = state.alarm.clear() {
                error!("Failed to clear alarm {:?}", e);
            }
            if let Some(clear_evt) = state.clear_evt.take() {
                if let Err(e) = clear_evt.signal() {
                    error!("failed to clear rtc pm signal {:?}", e);
                }
            }
        }
        let needs_worker = self.alarm_time.is_some();
        drop(state);
        if needs_worker && self.worker.is_none() {
            self.spawn_worker(self.alarm_state.clone());
        }
    }
}
fn run_cmos_worker(alarm_state: Arc<Mutex<AlarmState>>, kill_evt: Event) -> anyhow::Result<()> {
    #[derive(EventToken)]
    enum Token {
        Alarm,
        Kill,
    }
    let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
        (&alarm_state.lock().alarm, Token::Alarm),
        (&kill_evt, Token::Kill),
    ])
    .context("worker context failed")?;
    loop {
        let events = wait_ctx.wait().context("wait failed")?;
        let mut state = alarm_state.lock();
        for event in events.iter().filter(|e| e.is_readable) {
            match event.token {
                Token::Alarm => {
                    if state.alarm.mark_waited().context("timer ack failed")? {
                        continue;
                    }
                    match state.trigger_rtc_interrupt() {
                        Ok(clear_evt) => state.clear_evt = Some(clear_evt),
                        Err(e) => error!("Failed to send rtc {:?}", e),
                    }
                }
                Token::Kill => return Ok(()),
            }
        }
    }
}
fn from_bcd(v: u8) -> Option<u32> {
    let ones = (v & 0xf) as u32;
    let tens = (v >> 4) as u32;
    if ones < 10 && tens < 10 {
        Some(10 * tens + ones)
    } else {
        None
    }
}
fn alarm_from_registers(year: i32, data: &[u8; DATA_LEN]) -> Option<DateTime<Utc>> {
    Utc.with_ymd_and_hms(
        year,
        from_bcd(data[RTC_REG_ALARM_MONTH as usize])?,
        from_bcd(data[RTC_REG_ALARM_DAY as usize])?,
        from_bcd(data[RTC_REG_ALARM_HOUR as usize])?,
        from_bcd(data[RTC_REG_ALARM_MIN as usize])?,
        from_bcd(data[RTC_REG_ALARM_SEC as usize])?,
    )
    .single()
}
impl BusDevice for Cmos {
    fn device_id(&self) -> DeviceId {
        CrosvmDeviceId::Cmos.into()
    }
    fn debug_label(&self) -> String {
        "cmos".to_owned()
    }
    fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
        if data.len() != 1 {
            return;
        }
        match info.offset {
            INDEX_OFFSET => self.index = data[0] & INDEX_MASK,
            DATA_OFFSET => {
                let mut data = data[0];
                if self.index == RTC_REG_B {
                    if data & RTC_REG_B_UNSUPPORTED != 0 {
                        info!(
                            "Ignoring unsupported bits: {:x}",
                            data & RTC_REG_B_UNSUPPORTED
                        );
                        data &= !RTC_REG_B_UNSUPPORTED;
                    }
                    if data & RTC_REG_B_24_HOUR_MODE == 0 {
                        info!("12-hour mode unsupported");
                        data |= RTC_REG_B_24_HOUR_MODE;
                    }
                }
                self.data[self.index as usize] = data;
                if self.index == RTC_REG_B {
                    self.set_alarm();
                }
            }
            o => panic!("bad write offset on CMOS device: {}", o),
        }
    }
    fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
        fn to_bcd(v: u8) -> u8 {
            assert!(v < 100);
            ((v / 10) << 4) | (v % 10)
        }
        if data.len() != 1 {
            return;
        }
        data[0] = match info.offset {
            INDEX_OFFSET => self.index,
            DATA_OFFSET => {
                let now = (self.now_fn)();
                let seconds = now.second(); let minutes = now.minute(); let hours = now.hour(); let week_day = now.weekday().number_from_sunday(); let day = now.day(); let month = now.month(); let year = now.year();
                match self.index {
                    RTC_REG_SEC => to_bcd(seconds as u8),
                    RTC_REG_MIN => to_bcd(minutes as u8),
                    RTC_REG_HOUR => to_bcd(hours as u8),
                    RTC_REG_WEEK_DAY => to_bcd(week_day as u8),
                    RTC_REG_DAY => to_bcd(day as u8),
                    RTC_REG_MONTH => to_bcd(month as u8),
                    RTC_REG_YEAR => to_bcd((year % 100) as u8),
                    RTC_REG_CENTURY => to_bcd((year / 100) as u8),
                    RTC_REG_C => {
                        if self.alarm_time.is_some_and(|alarm_time| alarm_time <= now) {
                            self.alarm_time.take();
                            RTC_REG_C_IRQF | RTC_REG_C_AF
                        } else {
                            0
                        }
                    }
                    RTC_REG_D => RTC_REG_D_VRT,
                    _ => {
                        self.data[(self.index & INDEX_MASK) as usize]
                    }
                }
            }
            o => panic!("bad read offset on CMOS device: {}", o),
        }
    }
}
impl Suspendable for Cmos {
    fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
        AnySnapshot::to_any(self).context("failed to serialize Cmos")
    }
    fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
        #[derive(Deserialize)]
        struct CmosIndex {
            index: u8,
            #[serde(deserialize_with = "deserialize_seq_to_arr")]
            data: [u8; DATA_LEN],
        }
        let deser: CmosIndex = AnySnapshot::from_any(data).context("failed to deserialize Cmos")?;
        self.index = deser.index;
        self.data = deser.data;
        self.set_alarm();
        Ok(())
    }
    fn sleep(&mut self) -> anyhow::Result<()> {
        if let Some(worker) = self.worker.take() {
            worker.stop();
        }
        Ok(())
    }
    fn wake(&mut self) -> anyhow::Result<()> {
        self.spawn_worker(self.alarm_state.clone());
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::suspendable_tests;
    fn read_reg(cmos: &mut Cmos, reg: u8) -> u8 {
        cmos.write(
            BusAccessInfo {
                offset: 0,
                address: 0x70,
                id: 0,
            },
            &[reg],
        );
        let mut data = [0u8];
        cmos.read(
            BusAccessInfo {
                offset: 1,
                address: 0x71,
                id: 0,
            },
            &mut data,
        );
        data[0]
    }
    fn write_reg(cmos: &mut Cmos, reg: u8, val: u8) {
        cmos.write(
            BusAccessInfo {
                offset: 0,
                address: 0x70,
                id: 0,
            },
            &[reg],
        );
        let data = [val];
        cmos.write(
            BusAccessInfo {
                offset: 1,
                address: 0x71,
                id: 0,
            },
            &data,
        );
    }
    fn timestamp_to_datetime(timestamp: i64) -> DateTime<Utc> {
        DateTime::from_timestamp(timestamp, 0).unwrap()
    }
    fn test_now_party_like_its_1999() -> DateTime<Utc> {
        timestamp_to_datetime(946684799)
    }
    fn test_now_y2k_compliant() -> DateTime<Utc> {
        timestamp_to_datetime(946684800)
    }
    fn test_now_2016_before_leap_second() -> DateTime<Utc> {
        timestamp_to_datetime(1483228799)
    }
    fn test_now_2017_after_leap_second() -> DateTime<Utc> {
        timestamp_to_datetime(1483228800)
    }
    fn new_cmos_for_test(now_fn: CmosNowFn) -> Cmos {
        let irq = IrqEdgeEvent::new().unwrap();
        Cmos::new(1024, 0, now_fn, Tube::pair().unwrap().0, irq).unwrap()
    }
    #[test]
    fn cmos_write_index() {
        let mut cmos = new_cmos_for_test(test_now_party_like_its_1999);
        cmos.write(
            BusAccessInfo {
                offset: 0,
                address: 0x71,
                id: 0,
            },
            &[0x41],
        );
        assert_eq!(cmos.index, 0x41);
    }
    #[test]
    fn cmos_write_data() {
        let mut cmos = new_cmos_for_test(test_now_party_like_its_1999);
        cmos.write(
            BusAccessInfo {
                offset: 0,
                address: 0x71,
                id: 0,
            },
            &[0x41],
        );
        cmos.write(
            BusAccessInfo {
                offset: 1,
                address: 0x71,
                id: 0,
            },
            &[0x01],
        );
        assert_eq!(cmos.data[0x41], 0x01);
    }
    fn modify_device(cmos: &mut Cmos) {
        let info_index = BusAccessInfo {
            offset: 0,
            address: 0x71,
            id: 0,
        };
        let info_data = BusAccessInfo {
            offset: 1,
            address: 0x71,
            id: 0,
        };
        cmos.write(info_index, &[0x42]);
        cmos.write(info_data, &[0x01]);
    }
    #[test]
    fn cmos_date_time_1999() {
        let mut cmos = new_cmos_for_test(test_now_party_like_its_1999);
        assert_eq!(read_reg(&mut cmos, 0x00), 0x59); assert_eq!(read_reg(&mut cmos, 0x02), 0x59); assert_eq!(read_reg(&mut cmos, 0x04), 0x23); assert_eq!(read_reg(&mut cmos, 0x06), 0x06); assert_eq!(read_reg(&mut cmos, 0x07), 0x31); assert_eq!(read_reg(&mut cmos, 0x08), 0x12); assert_eq!(read_reg(&mut cmos, 0x09), 0x99); assert_eq!(read_reg(&mut cmos, 0x32), 0x19); }
    #[test]
    fn cmos_date_time_2000() {
        let mut cmos = new_cmos_for_test(test_now_y2k_compliant);
        assert_eq!(read_reg(&mut cmos, 0x00), 0x00); assert_eq!(read_reg(&mut cmos, 0x02), 0x00); assert_eq!(read_reg(&mut cmos, 0x04), 0x00); assert_eq!(read_reg(&mut cmos, 0x06), 0x07); assert_eq!(read_reg(&mut cmos, 0x07), 0x01); assert_eq!(read_reg(&mut cmos, 0x08), 0x01); assert_eq!(read_reg(&mut cmos, 0x09), 0x00); assert_eq!(read_reg(&mut cmos, 0x32), 0x20); }
    #[test]
    fn cmos_date_time_before_leap_second() {
        let mut cmos = new_cmos_for_test(test_now_2016_before_leap_second);
        assert_eq!(read_reg(&mut cmos, 0x00), 0x59); assert_eq!(read_reg(&mut cmos, 0x02), 0x59); assert_eq!(read_reg(&mut cmos, 0x04), 0x23); assert_eq!(read_reg(&mut cmos, 0x06), 0x07); assert_eq!(read_reg(&mut cmos, 0x07), 0x31); assert_eq!(read_reg(&mut cmos, 0x08), 0x12); assert_eq!(read_reg(&mut cmos, 0x09), 0x16); assert_eq!(read_reg(&mut cmos, 0x32), 0x20); }
    #[test]
    fn cmos_date_time_after_leap_second() {
        let mut cmos = new_cmos_for_test(test_now_2017_after_leap_second);
        assert_eq!(read_reg(&mut cmos, 0x00), 0x00); assert_eq!(read_reg(&mut cmos, 0x02), 0x00); assert_eq!(read_reg(&mut cmos, 0x04), 0x00); assert_eq!(read_reg(&mut cmos, 0x06), 0x01); assert_eq!(read_reg(&mut cmos, 0x07), 0x01); assert_eq!(read_reg(&mut cmos, 0x08), 0x01); assert_eq!(read_reg(&mut cmos, 0x09), 0x17); assert_eq!(read_reg(&mut cmos, 0x32), 0x20); }
    #[test]
    fn cmos_alarm() {
        let now_fn = || timestamp_to_datetime(946782245);
        let mut cmos = new_cmos_for_test(now_fn);
        write_reg(&mut cmos, 0x01, 0x06); write_reg(&mut cmos, 0x03, 0x05); write_reg(&mut cmos, 0x05, 0x04); write_reg(&mut cmos, 0x33, 0x03); write_reg(&mut cmos, 0x34, 0x02); write_reg(&mut cmos, 0x0b, 0x20); assert_eq!(cmos.alarm_time, Some(timestamp_to_datetime(949550706)));
        write_reg(&mut cmos, 0x01, 0x04); write_reg(&mut cmos, 0x03, 0x04); write_reg(&mut cmos, 0x05, 0x03); write_reg(&mut cmos, 0x33, 0x02); write_reg(&mut cmos, 0x34, 0x01); write_reg(&mut cmos, 0x0b, 0x20); assert_eq!(cmos.alarm_time, Some(timestamp_to_datetime(978404644)));
        write_reg(&mut cmos, 0x01, 0x05); write_reg(&mut cmos, 0x03, 0x04); write_reg(&mut cmos, 0x05, 0x03); write_reg(&mut cmos, 0x33, 0x02); write_reg(&mut cmos, 0x34, 0x01); write_reg(&mut cmos, 0x0b, 0x20); assert_eq!(cmos.alarm_time, Some(timestamp_to_datetime(946782245)));
        assert_eq!(read_reg(&mut cmos, 0x0c), 0xa0); assert_eq!(cmos.alarm_time, None);
        assert_eq!(read_reg(&mut cmos, 0x0c), 0);
        write_reg(&mut cmos, 0x01, 0xa0); write_reg(&mut cmos, 0x0b, 0x20); assert_eq!(cmos.alarm_time, None);
    }
    #[test]
    fn cmos_reg_d() {
        let mut cmos = new_cmos_for_test(test_now_party_like_its_1999);
        assert_eq!(read_reg(&mut cmos, 0x0d), 0x80) }
    #[test]
    fn cmos_snapshot_restore() -> anyhow::Result<()> {
        let mut cmos = new_cmos_for_test(test_now_party_like_its_1999);
        let info_index = BusAccessInfo {
            offset: 0,
            address: 0x71,
            id: 0,
        };
        let info_data = BusAccessInfo {
            offset: 1,
            address: 0x71,
            id: 0,
        };
        cmos.write(info_index, &[0x41]);
        cmos.write(info_data, &[0x01]);
        let snap = cmos.snapshot().context("failed to snapshot Cmos")?;
        cmos.write(info_index, &[0x42]);
        cmos.write(info_data, &[0x01]);
        cmos.restore(snap).context("failed to restore Cmos")?;
        assert_eq!(cmos.index, 0x41);
        assert_eq!(cmos.data[0x41], 0x01);
        assert_ne!(cmos.data[0x42], 0x01);
        Ok(())
    }
    #[test]
    fn cmos_sleep_wake() {
        let irq = IrqEdgeEvent::new().unwrap();
        let now_fn = || timestamp_to_datetime(946782245);
        let mut cmos = Cmos::new(1024, 0, now_fn, Tube::pair().unwrap().0, irq).unwrap();
        write_reg(&mut cmos, 0x01, 0x06); write_reg(&mut cmos, 0x03, 0x05); write_reg(&mut cmos, 0x05, 0x04); write_reg(&mut cmos, 0x33, 0x03); write_reg(&mut cmos, 0x34, 0x02); write_reg(&mut cmos, 0x0b, 0x20); assert_eq!(cmos.alarm_time, Some(timestamp_to_datetime(949550706)));
        assert!(cmos.worker.is_some());
        cmos.sleep().unwrap();
        assert!(cmos.worker.is_none());
        cmos.wake().unwrap();
        assert!(cmos.worker.is_some());
    }
    suspendable_tests!(
        cmos1999,
        new_cmos_for_test(test_now_party_like_its_1999),
        modify_device
    );
    suspendable_tests!(
        cmos2k,
        new_cmos_for_test(test_now_y2k_compliant),
        modify_device
    );
    suspendable_tests!(
        cmos2016,
        new_cmos_for_test(test_now_2016_before_leap_second),
        modify_device
    );
    suspendable_tests!(
        cmos2017,
        new_cmos_for_test(test_now_2017_after_leap_second),
        modify_device
    );
}