use super::ProcState;
use super::StatFlags;
#[cfg(feature = "chrono")]
use crate::TICKS_PER_SECOND;
use crate::{from_iter, KernelVersion, ProcResult};
use crate::{ProcError, KERNEL, PAGESIZE};
#[cfg(feature = "serde1")]
use serde::{Deserialize, Serialize};

use std::io::Read;
use std::str::FromStr;

macro_rules! since_kernel {
    ($a:tt, $b:tt, $c:tt, $e:expr) => {
        if let Ok(kernel) = *KERNEL {
            if kernel >= KernelVersion::new($a, $b, $c) {
                Some($e)
            } else {
                None
            }
        } else {
            None
        }
    };
}

/// Status information about the process, based on the `/proc/<pid>/stat` file.
///
/// To construct one of these structures, you have to first create a [Process](crate::process::Process).
///
/// Not all fields are available in every kernel.  These fields have `Option<T>` types.
///
/// New fields to this struct may be added at any time (even without a major or minor semver bump).
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct Stat {
    /// The process ID.
    pub pid: i32,
    /// The filename of the executable, in parentheses.
    ///
    /// This is visible whether or not the executable is swapped out.
    ///
    /// Note that if the actual comm field contains invalid UTF-8 characters, they will be replaced
    /// here by the U+FFFD replacement character.
    pub comm: String,
    /// Process State.
    ///
    /// See [state()](#method.state) to get the process state as an enum.
    pub state: char,
    /// The PID of the parent of this process.
    pub ppid: i32,
    /// The process group ID of the process.
    pub pgrp: i32,
    /// The session ID of the process.
    pub session: i32,
    /// The controlling terminal of the process.
    ///
    /// The minor device number is contained in the combination of bits 31 to 20 and  7  to  0;
    /// the major device number is in bits 15 to 8.
    ///
    /// See [tty_nr()](#method.tty_nr) to get this value decoded into a (major, minor) tuple
    pub tty_nr: i32,
    /// The ID of the foreground process group of the controlling terminal of the process.
    pub tpgid: i32,
    /// The kernel flags  word of the process.
    ///
    /// For bit meanings, see the PF_* defines in  the  Linux  kernel  source  file
    /// [`include/linux/sched.h`](https://github.com/torvalds/linux/blob/master/include/linux/sched.h).
    ///
    /// See [flags()](#method.flags) to get a [`StatFlags`](struct.StatFlags.html) bitfield object.
    pub flags: u32,
    /// The number of minor faults the process has made which have not required loading a memory
    /// page from disk.
    pub minflt: u64,
    /// The number of minor faults that the process's waited-for children have made.
    pub cminflt: u64,
    /// The number of major faults the process has made which have required loading a memory page
    /// from disk.
    pub majflt: u64,
    /// The number of major faults that the process's waited-for children have made.
    pub cmajflt: u64,
    /// Amount of time that this process has been scheduled in user mode, measured in clock ticks
    /// (divide by [`ticks_per_second()`](crate::ticks_per_second).
    ///
    /// This includes guest time, guest_time (time spent running a virtual CPU, see below), so that
    /// applications that are not aware of the guest time field  do not lose that time from their
    /// calculations.
    pub utime: u64,
    /// Amount of time that this process has been scheduled in kernel mode, measured in clock ticks
    /// (divide by [`ticks_per_second()`](crate::ticks_per_second)).
    pub stime: u64,

    /// Amount  of  time  that  this  process's  waited-for  children  have  been  scheduled  in
    /// user  mode,  measured  in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second)).
    ///
    /// This includes guest time, cguest_time (time spent running a virtual CPU, see below).
    pub cutime: i64,

    /// Amount of time that this process's waited-for  children  have  been  scheduled  in  kernel
    /// mode,  measured  in  clock  ticks  (divide  by [`ticks_per_second()`](crate::ticks_per_second)).
    pub cstime: i64,
    /// For processes running a real-time scheduling policy (policy below; see sched_setscheduler(2)),
    /// this is the negated scheduling priority, minus one;
    ///
    /// That is, a number in the range -2 to -100,
    /// corresponding to real-time priority 1 to 99.  For processes running under a non-real-time
    /// scheduling policy, this is the raw nice value (setpriority(2)) as represented in the kernel.
    /// The kernel stores nice values as numbers in the range 0 (high) to 39  (low),  corresponding
    /// to the user-visible nice range of -20 to 19.
    /// (This explanation is for Linux 2.6)
    ///
    /// Before Linux 2.6, this was a scaled value based on the scheduler weighting given to this process.
    pub priority: i64,
    /// The nice value (see `setpriority(2)`), a value in the range 19 (low priority) to -20 (high priority).
    pub nice: i64,
    /// Number  of  threads in this process (since Linux 2.6).  Before kernel 2.6, this field was
    /// hard coded to 0 as a placeholder for an earlier removed field.
    pub num_threads: i64,
    /// The time in jiffies before the next SIGALRM is sent to the process due to an interval
    /// timer.
    ///
    /// Since kernel 2.6.17, this  field is no longer maintained, and is hard coded as 0.
    pub itrealvalue: i64,
    /// The time the process started after system boot.
    ///
    /// In kernels before Linux 2.6, this value was expressed in  jiffies.  Since  Linux 2.6, the
    /// value is expressed in clock ticks (divide by `sysconf(_SC_CLK_TCK)`).
    ///
    #[cfg_attr(
        feature = "chrono",
        doc = "See also the [Stat::starttime()] method to get the starttime as a `DateTime` object"
    )]
    #[cfg_attr(
        not(feature = "chrono"),
        doc = "If you compile with the optional `chrono` feature, you can use the `starttime()` method to get the starttime as a `DateTime` object"
    )]
    pub starttime: u64,
    /// Virtual memory size in bytes.
    pub vsize: u64,
    /// Resident Set Size: number of pages the process has in real memory.
    ///
    /// This is just the pages which count toward text,  data,  or stack space.
    /// This does not include pages which have not been demand-loaded in, or which are swapped out.
    pub rss: u64,
    /// Current soft limit in bytes on the rss of the process; see the description of RLIMIT_RSS in
    /// getrlimit(2).
    pub rsslim: u64,
    /// The address above which program text can run.
    pub startcode: u64,
    /// The address below which program text can run.
    pub endcode: u64,
    /// The address of the start (i.e., bottom) of the stack.
    pub startstack: u64,
    /// The current value of ESP (stack pointer), as found in the kernel stack page for the
    /// process.
    pub kstkesp: u64,
    /// The current EIP (instruction pointer).
    pub kstkeip: u64,
    /// The  bitmap of pending signals, displayed as a decimal number.  Obsolete, because it does
    /// not provide information on real-time signals; use `/proc/<pid>/status` instead.
    pub signal: u64,
    /// The bitmap of blocked signals, displayed as a decimal number.  Obsolete, because it does
    /// not provide information on  real-time signals; use `/proc/<pid>/status` instead.
    pub blocked: u64,
    /// The  bitmap of ignored signals, displayed as a decimal number.  Obsolete, because it does
    /// not provide information on real-time signals; use `/proc/<pid>/status` instead.
    pub sigignore: u64,
    /// The bitmap of caught signals, displayed as a decimal number.  Obsolete, because it does not
    /// provide information  on  real-time signals; use `/proc/<pid>/status` instead.
    pub sigcatch: u64,
    /// This  is  the  "channel"  in which the process is waiting.  It is the address of a location
    /// in the kernel where the process is sleeping.  The corresponding symbolic name can be found in
    /// `/proc/<pid>/wchan`.
    pub wchan: u64,
    /// Number of pages swapped **(not maintained)**.
    pub nswap: u64,
    /// Cumulative nswap for child processes **(not maintained)**.
    pub cnswap: u64,
    /// Signal to be sent to parent when we die.
    ///
    /// (since Linux 2.1.22)
    pub exit_signal: Option<i32>,
    /// CPU number last executed on.
    ///
    /// (since Linux 2.2.8)
    pub processor: Option<i32>,
    /// Real-time scheduling priority
    ///
    ///  Real-time scheduling priority, a number in the range 1 to 99 for processes scheduled under a real-time policy, or 0, for non-real-time processes
    ///
    /// (since Linux 2.5.19)
    pub rt_priority: Option<u32>,
    /// Scheduling policy (see sched_setscheduler(2)).
    ///
    /// Decode using the `SCHED_*` constants in `linux/sched.h`.
    ///
    /// (since Linux 2.5.19)
    pub policy: Option<u32>,
    /// Aggregated block I/O delays, measured in clock ticks (centiseconds).
    ///
    /// (since Linux 2.6.18)
    pub delayacct_blkio_ticks: Option<u64>,
    /// Guest time of the process (time spent running a virtual CPU for a guest operating system),
    /// measured in clock ticks (divide by [`ticks_per_second()`](crate::ticks_per_second))
    ///
    /// (since Linux 2.6.24)
    pub guest_time: Option<u64>,
    /// Guest time of the process's children, measured in clock ticks (divide by
    /// [`ticks_per_second()`](crate::ticks_per_second)).
    ///
    /// (since Linux 2.6.24)
    pub cguest_time: Option<i64>,
    /// Address above which program initialized and uninitialized (BSS) data are placed.
    ///
    /// (since Linux 3.3)
    pub start_data: Option<u64>,
    /// Address below which program initialized and uninitialized (BSS) data are placed.
    ///
    /// (since Linux 3.3)
    pub end_data: Option<u64>,
    /// Address above which program heap can be expanded with brk(2).
    ///
    /// (since Linux 3.3)
    pub start_brk: Option<u64>,
    /// Address above which program command-line arguments (argv) are placed.
    ///
    /// (since Linux 3.5)
    pub arg_start: Option<u64>,
    /// Address below program command-line arguments (argv) are placed.
    ///
    /// (since Linux 3.5)
    pub arg_end: Option<u64>,
    /// Address above which program environment is placed.
    ///
    /// (since Linux 3.5)
    pub env_start: Option<u64>,
    /// Address below which program environment is placed.
    ///
    /// (since Linux 3.5)
    pub env_end: Option<u64>,
    /// The thread's exit status in the form reported by waitpid(2).
    ///
    /// (since Linux 3.5)
    pub exit_code: Option<i32>,
}

impl Stat {
    #[allow(clippy::cognitive_complexity)]
    pub fn from_reader<R: Read>(mut r: R) -> ProcResult<Stat> {
        // read in entire thing, this is only going to be 1 line
        let mut buf = Vec::with_capacity(512);
        r.read_to_end(&mut buf)?;

        let line = String::from_utf8_lossy(&buf);
        let buf = line.trim();

        // find the first opening paren, and split off the first part (pid)
        let start_paren = expect!(buf.find('('));
        let end_paren = expect!(buf.rfind(')'));
        let pid_s = &buf[..start_paren - 1];
        let comm = buf[start_paren + 1..end_paren].to_string();
        let rest = &buf[end_paren + 2..];

        let pid = expect!(FromStr::from_str(pid_s));

        let mut rest = rest.split(' ');
        let state = expect!(expect!(rest.next()).chars().next());

        let ppid = expect!(from_iter(&mut rest));
        let pgrp = expect!(from_iter(&mut rest));
        let session = expect!(from_iter(&mut rest));
        let tty_nr = expect!(from_iter(&mut rest));
        let tpgid = expect!(from_iter(&mut rest));
        let flags = expect!(from_iter(&mut rest));
        let minflt = expect!(from_iter(&mut rest));
        let cminflt = expect!(from_iter(&mut rest));
        let majflt = expect!(from_iter(&mut rest));
        let cmajflt = expect!(from_iter(&mut rest));
        let utime = expect!(from_iter(&mut rest));
        let stime = expect!(from_iter(&mut rest));
        let cutime = expect!(from_iter(&mut rest));
        let cstime = expect!(from_iter(&mut rest));
        let priority = expect!(from_iter(&mut rest));
        let nice = expect!(from_iter(&mut rest));
        let num_threads = expect!(from_iter(&mut rest));
        let itrealvalue = expect!(from_iter(&mut rest));
        let starttime = expect!(from_iter(&mut rest));
        let vsize = expect!(from_iter(&mut rest));
        let rss = expect!(from_iter(&mut rest));
        let rsslim = expect!(from_iter(&mut rest));
        let startcode = expect!(from_iter(&mut rest));
        let endcode = expect!(from_iter(&mut rest));
        let startstack = expect!(from_iter(&mut rest));
        let kstkesp = expect!(from_iter(&mut rest));
        let kstkeip = expect!(from_iter(&mut rest));
        let signal = expect!(from_iter(&mut rest));
        let blocked = expect!(from_iter(&mut rest));
        let sigignore = expect!(from_iter(&mut rest));
        let sigcatch = expect!(from_iter(&mut rest));
        let wchan = expect!(from_iter(&mut rest));
        let nswap = expect!(from_iter(&mut rest));
        let cnswap = expect!(from_iter(&mut rest));

        let exit_signal = since_kernel!(2, 1, 22, expect!(from_iter(&mut rest)));
        let processor = since_kernel!(2, 2, 8, expect!(from_iter(&mut rest)));
        let rt_priority = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest)));
        let policy = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest)));
        let delayacct_blkio_ticks = since_kernel!(2, 6, 18, expect!(from_iter(&mut rest)));
        let guest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest)));
        let cguest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest)));
        let start_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest)));
        let end_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest)));
        let start_brk = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest)));
        let arg_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest)));
        let arg_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest)));
        let env_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest)));
        let env_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest)));
        let exit_code = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest)));

        Ok(Stat {
            pid,
            comm,
            state,
            ppid,
            pgrp,
            session,
            tty_nr,
            tpgid,
            flags,
            minflt,
            cminflt,
            majflt,
            cmajflt,
            utime,
            stime,
            cutime,
            cstime,
            priority,
            nice,
            num_threads,
            itrealvalue,
            starttime,
            vsize,
            rss,
            rsslim,
            startcode,
            endcode,
            startstack,
            kstkesp,
            kstkeip,
            signal,
            blocked,
            sigignore,
            sigcatch,
            wchan,
            nswap,
            cnswap,
            exit_signal,
            processor,
            rt_priority,
            policy,
            delayacct_blkio_ticks,
            guest_time,
            cguest_time,
            start_data,
            end_data,
            start_brk,
            arg_start,
            arg_end,
            env_start,
            env_end,
            exit_code,
        })
    }

    pub fn state(&self) -> ProcResult<ProcState> {
        ProcState::from_char(self.state)
            .ok_or_else(|| build_internal_error!(format!("{:?} is not a recognized process state", self.state)))
    }

    pub fn tty_nr(&self) -> (i32, i32) {
        // minor is bits 31-20 and 7-0
        // major is 15-8

        // mmmmmmmmmmmm____MMMMMMMMmmmmmmmm
        // 11111111111100000000000000000000
        let major = (self.tty_nr & 0xfff00) >> 8;
        let minor = (self.tty_nr & 0x000ff) | ((self.tty_nr >> 12) & 0xfff00);
        (major, minor)
    }

    /// The kernel flags word of the process, as a bitfield
    ///
    /// See also the [Stat::flags](struct.Stat.html#structfield.flags) field.
    pub fn flags(&self) -> ProcResult<StatFlags> {
        StatFlags::from_bits(self.flags)
            .ok_or_else(|| build_internal_error!(format!("Can't construct flags bitfield from {:?}", self.flags)))
    }

    /// Get the starttime of the process as a `DateTime` object.
    ///
    /// See also the [`starttime`](struct.Stat.html#structfield.starttime) field.
    ///
    /// This function requires the "chrono" features to be enabled (which it is by default).
    #[cfg(feature = "chrono")]
    pub fn starttime(&self) -> ProcResult<chrono::DateTime<chrono::Local>> {
        let tts = TICKS_PER_SECOND
            .as_ref()
            .map_err(|e| ProcError::Other(format!("Failed to get ticks_per_second: {:?}", e)))?;
        let seconds_since_boot = self.starttime as f32 / *tts as f32;
        let boot_time = crate::boot_time()?;

        Ok(boot_time + chrono::Duration::milliseconds((seconds_since_boot * 1000.0) as i64))
    }

    /// Gets the Resident Set Size (in bytes)
    ///
    /// The `rss` field will return the same value in pages
    pub fn rss_bytes(&self) -> ProcResult<u64> {
        let pagesize = PAGESIZE
            .as_ref()
            .map_err(|e| ProcError::Other(format!("Failed to get pagesize: {:?}", e)))?;
        Ok(self.rss * *pagesize)
    }
}
