// Copyright (c) 2020 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::time::Instant;

use address_space::GuestAddress;
use byteorder::{ByteOrder, LittleEndian};
use log::error;

// Frequency of PM Timer in HZ.
const PM_TIMER_FREQUENCY: u128 = 3_579_545;
const NANOSECONDS_PER_SECOND: u128 = 1_000_000_000;
pub const ACPI_BITMASK_SLEEP_ENABLE: u16 = 0x2000;

/// ACPI Power Management Timer
#[allow(clippy::upper_case_acronyms)]
pub struct AcpiPMTimer {
    start: Instant,
}

impl Default for AcpiPMTimer {
    fn default() -> Self {
        Self::new()
    }
}

impl AcpiPMTimer {
    pub fn new() -> AcpiPMTimer {
        AcpiPMTimer {
            start: Instant::now(),
        }
    }

    pub fn read(&mut self, data: &mut [u8], _base: GuestAddress, _offset: u64) -> bool {
        if data.len() != 4 {
            error!(
                "PM Timer read: invalid data length {}, required length is 4",
                data.len()
            );
        }
        let now = Instant::now();
        let time_nanos = now.duration_since(self.start).as_nanos();
        let counter: u128 = (time_nanos * PM_TIMER_FREQUENCY) / NANOSECONDS_PER_SECOND;

        data.copy_from_slice(&((counter & 0xFFFF_FFFF) as u32).to_le_bytes());
        true
    }
}

#[derive(Default)]
pub struct AcpiPmEvent {
    // PM1 Status Registers, location: PM1a_EVT_BLK.
    status: u16,
    // PM1Enable Registers, location: PM1a_EVT_BLK + PM1_EVT_LEN / 2.
    enable: u16,
}

impl AcpiPmEvent {
    pub fn new() -> AcpiPmEvent {
        AcpiPmEvent {
            status: 0,
            enable: 0,
        }
    }

    pub fn read(&mut self, data: &mut [u8], _base: GuestAddress, offset: u64) -> bool {
        match offset {
            0 => match data.len() {
                1 => data[0] = self.status as u8,
                2 => LittleEndian::write_u16(data, self.status),
                n => {
                    error!(
                        "Invalid data length {} for reading PM status register, offset is {}",
                        n, offset
                    );
                    return false;
                }
            },
            2 => match data.len() {
                1 => data[0] = self.enable as u8,
                2 => LittleEndian::write_u16(data, self.enable),
                n => {
                    error!(
                        "Invalid data length {} for reading PM enable register, offset is {}",
                        n, offset
                    );
                    return false;
                }
            },
            _ => {
                error!("Invalid offset");
                return false;
            }
        }
        true
    }

    pub fn write(&mut self, data: &[u8], _base: GuestAddress, offset: u64) -> bool {
        match offset {
            0 => {
                let value: u16 = match data.len() {
                    1 => data[0] as u16,
                    2 => LittleEndian::read_u16(data),
                    n => {
                        error!(
                            "Invalid data length {} for writing PM status register, offset is {}",
                            n, offset
                        );
                        return false;
                    }
                };
                self.status &= !value;
            }
            2 => {
                let value: u16 = match data.len() {
                    1 => data[0] as u16,
                    2 => LittleEndian::read_u16(data),
                    n => {
                        error!(
                            "Invalid data length {} for writing PM enable register, offset is {}",
                            n, offset
                        );
                        return false;
                    }
                };
                self.enable = value;
            }
            _ => {
                error!("Invalid offset");
                return false;
            }
        }
        true
    }
}

#[derive(Default)]
pub struct AcpiPmCtrl {
    control: u16,
}

impl AcpiPmCtrl {
    pub fn new() -> AcpiPmCtrl {
        AcpiPmCtrl { control: 0 }
    }

    pub fn read(&mut self, data: &mut [u8], _base: GuestAddress, _offset: u64) -> bool {
        match data.len() {
            1 => data[0] = self.control as u8,
            2 => LittleEndian::write_u16(data, self.control),
            n => {
                error!("Invalid data length {} for reading PM control register", n);
                return false;
            }
        }
        true
    }

    // Return true when guest want poweroff.
    pub fn write(&mut self, data: &[u8], _base: GuestAddress, _offset: u64) -> bool {
        let value: u16 = match data.len() {
            1 => data[0] as u16,
            2 => LittleEndian::read_u16(data),
            n => {
                error!("Invalid data length {} for writing PM control register", n);
                return false;
            }
        };
        self.control = value & !ACPI_BITMASK_SLEEP_ENABLE;
        value & ACPI_BITMASK_SLEEP_ENABLE != 0
    }
}