// SPDX-License-Identifier: MPL-2.0

use core::time::Duration;

use int_to_c_enum::TryFromInt;

use super::SyscallReturn;
use crate::{
    prelude::*,
    process::{
        posix_thread::{thread_table, AsPosixThread},
        process_table,
    },
    time::{
        clockid_t,
        clocks::{
            BootTimeClock, MonotonicClock, MonotonicCoarseClock, MonotonicRawClock, RealTimeClock,
            RealTimeCoarseClock,
        },
        timespec_t, Clock,
    },
};

pub fn sys_clock_gettime(
    clockid: clockid_t,
    timespec_addr: Vaddr,
    ctx: &Context,
) -> Result<SyscallReturn> {
    debug!("clockid = {:?}", clockid);

    let time_duration = read_clock(clockid, ctx)?;

    let timespec = timespec_t::from(time_duration);
    ctx.user_space().write_val(timespec_addr, &timespec)?;

    Ok(SyscallReturn::Return(0))
}

// The hard-coded clock IDs.
#[derive(Debug, Copy, Clone, TryFromInt, PartialEq)]
#[repr(i32)]
#[allow(non_camel_case_types)]
pub enum ClockId {
    CLOCK_REALTIME = 0,
    CLOCK_MONOTONIC = 1,
    CLOCK_PROCESS_CPUTIME_ID = 2,
    CLOCK_THREAD_CPUTIME_ID = 3,
    CLOCK_MONOTONIC_RAW = 4,
    CLOCK_REALTIME_COARSE = 5,
    CLOCK_MONOTONIC_COARSE = 6,
    CLOCK_BOOTTIME = 7,
}

/// The information decoded from a dynamic clock ID.
///
/// Dynamic clocks are the clocks operates on certain
/// character devices, processes or threads. Their IDs will
/// be generated by encoding the file descriptor, PID or TID.
/// Here we follow the rules in Linux:
///
/// The dynamic clock ID is a 32 bit integer.
/// - The most significant 29 bits hold either a PID or a file descriptor.
/// - Bit 2 indicates whether a cpu clock refers to a thread or a process.
/// - Bits 1 and 0 give the type: PROF=0, VIRT=1, SCHED=2, or FD=3.
/// - A clock ID is invalid if bits 2, 1, and 0 are all set.
///
/// Ref: https://github.com/torvalds/linux/blob/master/include/linux/posix-timers_types.h
pub enum DynamicClockIdInfo {
    Pid(u32, DynamicClockType),
    Tid(u32, DynamicClockType),
    #[allow(dead_code)]
    Fd(u32),
}

impl TryFrom<clockid_t> for DynamicClockIdInfo {
    type Error = crate::Error;

    fn try_from(value: clockid_t) -> core::prelude::v1::Result<Self, Self::Error> {
        const CPU_CLOCK_TYPE_MASK: i32 = 0b11;
        const ID_TYPE_MASK: i32 = 0b100;
        const INVALID_MASK: i32 = CPU_CLOCK_TYPE_MASK | ID_TYPE_MASK;

        if (value & INVALID_MASK) == INVALID_MASK {
            return_errno_with_message!(Errno::EINVAL, "invalid clock ID");
        }

        let id = !(value >> 3);
        let cpu_clock_type = DynamicClockType::try_from(CPU_CLOCK_TYPE_MASK & value)?;

        if let DynamicClockType::FD = cpu_clock_type {
            return Ok(DynamicClockIdInfo::Fd(id as u32));
        }

        if ID_TYPE_MASK & value > 0 {
            Ok(DynamicClockIdInfo::Tid(id as u32, cpu_clock_type))
        } else {
            Ok(DynamicClockIdInfo::Pid(id as u32, cpu_clock_type))
        }
    }
}

#[derive(Debug, Copy, Clone, TryFromInt, PartialEq)]
#[repr(i32)]
pub enum DynamicClockType {
    Profiling = 0,
    Virtual = 1,
    Scheduling = 2,
    FD = 3,
}

/// Reads the time of a clock specified by the input clock ID.
///
/// If the clock ID does not support, this function will return `Err`.
pub fn read_clock(clockid: clockid_t, ctx: &Context) -> Result<Duration> {
    if clockid >= 0 {
        let clock_id = ClockId::try_from(clockid)?;
        match clock_id {
            ClockId::CLOCK_REALTIME => Ok(RealTimeClock::get().read_time()),
            ClockId::CLOCK_MONOTONIC => Ok(MonotonicClock::get().read_time()),
            ClockId::CLOCK_MONOTONIC_RAW => Ok(MonotonicRawClock::get().read_time()),
            ClockId::CLOCK_REALTIME_COARSE => Ok(RealTimeCoarseClock::get().read_time()),
            ClockId::CLOCK_MONOTONIC_COARSE => Ok(MonotonicCoarseClock::get().read_time()),
            ClockId::CLOCK_BOOTTIME => Ok(BootTimeClock::get().read_time()),
            ClockId::CLOCK_PROCESS_CPUTIME_ID => Ok(ctx.process.prof_clock().read_time()),
            ClockId::CLOCK_THREAD_CPUTIME_ID => Ok(ctx.posix_thread.prof_clock().read_time()),
        }
    } else {
        let dynamic_clockid_info = DynamicClockIdInfo::try_from(clockid)?;
        match dynamic_clockid_info {
            DynamicClockIdInfo::Pid(pid, clock_type) => {
                let process = process_table::get_process(pid)
                    .ok_or_else(|| crate::Error::with_message(Errno::EINVAL, "invalid clock ID"))?;
                match clock_type {
                    DynamicClockType::Profiling => Ok(process.prof_clock().read_time()),
                    DynamicClockType::Virtual => Ok(process.prof_clock().user_clock().read_time()),
                    // TODO: support scheduling clock and fd clock.
                    _ => unimplemented!(),
                }
            }
            DynamicClockIdInfo::Tid(tid, clock_type) => {
                let thread = thread_table::get_thread(tid)
                    .ok_or_else(|| Error::with_message(Errno::EINVAL, "invalid clock ID"))?;
                let posix_thread = thread.as_posix_thread().unwrap();
                match clock_type {
                    DynamicClockType::Profiling => Ok(posix_thread.prof_clock().read_time()),
                    DynamicClockType::Virtual => {
                        Ok(posix_thread.prof_clock().user_clock().read_time())
                    }
                    _ => unimplemented!(),
                }
            }
            DynamicClockIdInfo::Fd(_) => unimplemented!(),
        }
    }
}
