use std::{borrow::Cow, cmp::Reverse};

use serde::Deserialize;

use super::{ProcWidgetColumn, ProcWidgetData};
use crate::{
    canvas::components::data_table::{ColumnHeader, SortsRow},
    utils::general::sort_partial_fn,
};

/// A column in the process widget.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
#[cfg_attr(
    feature = "generate_schema",
    derive(schemars::JsonSchema, strum::VariantArray)
)]
pub enum ProcColumn {
    CpuPercent,
    MemValue,
    MemPercent,
    VirtualMem,
    Pid,
    Count,
    Name,
    Command,
    ReadPerSecond,
    WritePerSecond,
    TotalRead,
    TotalWrite,
    State,
    User,
    Time,
    #[cfg(unix)]
    Nice,
    Priority,
    #[cfg(feature = "gpu")]
    GpuMemValue,
    #[cfg(feature = "gpu")]
    GpuMemPercent,
    #[cfg(feature = "gpu")]
    GpuUtilPercent,
}

impl ProcColumn {
    /// An ugly hack to generate the JSON schema.
    #[cfg(feature = "generate_schema")]
    pub fn get_schema_names(&self) -> &[&'static str] {
        match self {
            ProcColumn::Pid => &["PID"],
            ProcColumn::Count => &["Count"],
            ProcColumn::Name => &["Name"],
            ProcColumn::Command => &["Command"],
            ProcColumn::CpuPercent => &["CPU%"],
            // TODO: Change this
            ProcColumn::MemValue | ProcColumn::MemPercent => &["Mem", "Mem%", "Memory", "Memory%"],
            ProcColumn::VirtualMem => &["Virt", "Virtual", "VirtMem", "Virtual Memory"],
            ProcColumn::ReadPerSecond => &["R/s", "Read", "Rps"],
            ProcColumn::WritePerSecond => &["W/s", "Write", "Wps"],
            ProcColumn::TotalRead => &["T.Read", "TRead", "Total Read"],
            ProcColumn::TotalWrite => &["T.Write", "TWrite", "Total Write"],
            ProcColumn::State => &["State"],
            ProcColumn::User => &["User"],
            ProcColumn::Time => &["Time"],
            #[cfg(feature = "gpu")]
            // TODO: Change this
            ProcColumn::GpuMemValue | ProcColumn::GpuMemPercent => &["GMem", "GMem%"],
            #[cfg(feature = "gpu")]
            ProcColumn::GpuUtilPercent => &["GPU%"],
            #[cfg(unix)]
            ProcColumn::Nice => &["Nice"],
            ProcColumn::Priority => &["Priority"],
        }
    }
}

impl ColumnHeader for ProcColumn {
    fn text(&self) -> Cow<'static, str> {
        match self {
            ProcColumn::CpuPercent => "CPU%",
            ProcColumn::MemValue => "Mem",
            ProcColumn::MemPercent => "Mem%",
            ProcColumn::VirtualMem => "Virt",
            ProcColumn::Pid => "PID",
            ProcColumn::Count => "Count",
            ProcColumn::Name => "Name",
            ProcColumn::Command => "Command",
            ProcColumn::ReadPerSecond => "R/s",
            ProcColumn::WritePerSecond => "W/s",
            ProcColumn::TotalRead => "T.Read",
            ProcColumn::TotalWrite => "T.Write",
            ProcColumn::State => "State",
            ProcColumn::User => "User",
            ProcColumn::Time => "Time",
            #[cfg(unix)]
            ProcColumn::Nice => "Nice",
            ProcColumn::Priority => "Priority",
            #[cfg(feature = "gpu")]
            ProcColumn::GpuMemValue => "GMem",
            #[cfg(feature = "gpu")]
            ProcColumn::GpuMemPercent => "GMem%",
            #[cfg(feature = "gpu")]
            ProcColumn::GpuUtilPercent => "GPU%",
        }
        .into()
    }

    fn header(&self) -> Cow<'static, str> {
        match self {
            ProcColumn::CpuPercent => "CPU%(c)".into(),
            ProcColumn::MemValue => "Mem(m)".into(),
            ProcColumn::MemPercent => "Mem%(m)".into(),
            ProcColumn::Pid => "PID(p)".into(),
            ProcColumn::Name => "Name(n)".into(),
            ProcColumn::Command => "Command(n)".into(),
            #[cfg(unix)]
            ProcColumn::Nice => "Nice".into(),
            ProcColumn::Priority => "Priority".into(),
            _ => self.text(),
        }
    }
}

impl SortsRow for ProcColumn {
    type DataType = ProcWidgetData;

    fn sort_data(&self, data: &mut [ProcWidgetData], descending: bool) {
        match self {
            ProcColumn::CpuPercent => {
                data.sort_by(|a, b| {
                    sort_partial_fn(descending)(a.cpu_usage_percent, b.cpu_usage_percent)
                });
            }
            ProcColumn::MemValue | ProcColumn::MemPercent => {
                data.sort_by(|a, b| sort_partial_fn(descending)(&a.mem_usage, &b.mem_usage));
            }
            ProcColumn::VirtualMem => {
                data.sort_by(|a, b| sort_partial_fn(descending)(&a.virtual_mem, &b.virtual_mem));
            }
            ProcColumn::Pid => {
                data.sort_by(|a, b| sort_partial_fn(descending)(a.pid, b.pid));
            }
            ProcColumn::Count => {
                data.sort_by(|a, b| sort_partial_fn(descending)(a.num_similar, b.num_similar));
            }
            ProcColumn::Name | ProcColumn::Command => {
                if descending {
                    data.sort_by_cached_key(|pd| Reverse(pd.id.to_lowercase()));
                } else {
                    data.sort_by_cached_key(|pd| pd.id.to_lowercase());
                }
            }
            ProcColumn::ReadPerSecond => {
                data.sort_by(|a, b| sort_partial_fn(descending)(a.rps, b.rps));
            }
            ProcColumn::WritePerSecond => {
                data.sort_by(|a, b| sort_partial_fn(descending)(a.wps, b.wps));
            }
            ProcColumn::TotalRead => {
                data.sort_by(|a, b| sort_partial_fn(descending)(a.total_read, b.total_read));
            }
            ProcColumn::TotalWrite => {
                data.sort_by(|a, b| sort_partial_fn(descending)(a.total_write, b.total_write));
            }
            ProcColumn::State => {
                if descending {
                    data.sort_by_cached_key(|pd| Reverse(pd.process_state));
                } else {
                    data.sort_by_cached_key(|pd| pd.process_state);
                }
            }
            ProcColumn::User => {
                // FIXME: Is there a better way here to keep the to_lowercase? Usually it
                // shouldn't matter but...
                if descending {
                    data.sort_by_cached_key(|pd| Reverse(pd.user.clone()));
                } else {
                    data.sort_by_cached_key(|pd| pd.user.clone());
                }
            }
            ProcColumn::Time => {
                data.sort_by(|a, b| sort_partial_fn(descending)(a.time, b.time));
            }
            ProcColumn::Priority => {
                data.sort_by(|a, b| sort_partial_fn(descending)(a.priority, b.priority));
            }
            #[cfg(unix)]
            ProcColumn::Nice => {
                data.sort_by(|a, b| sort_partial_fn(descending)(a.nice, b.nice));
            }
            #[cfg(feature = "gpu")]
            ProcColumn::GpuMemValue | ProcColumn::GpuMemPercent => {
                data.sort_by(|a, b| {
                    sort_partial_fn(descending)(&a.gpu_mem_usage, &b.gpu_mem_usage)
                });
            }
            #[cfg(feature = "gpu")]
            ProcColumn::GpuUtilPercent => {
                data.sort_by(|a, b| sort_partial_fn(descending)(a.gpu_usage, b.gpu_usage));
            }
        }
    }
}

impl ProcColumn {
    /// Parse a column name, case-insensitively, accepting any of the aliases
    /// recognized in the config file.
    pub fn parse_column_name(name: &str) -> Option<Self> {
        match name.to_lowercase().as_str() {
            "cpu%" => Some(ProcColumn::CpuPercent),
            "mem" | "mem%" => Some(ProcColumn::MemPercent),
            "virt" | "virtual" | "virtmem" | "virtual memory" => Some(ProcColumn::VirtualMem),
            "pid" => Some(ProcColumn::Pid),
            "count" => Some(ProcColumn::Count),
            "name" => Some(ProcColumn::Name),
            "command" => Some(ProcColumn::Command),
            "read" | "r/s" | "rps" => Some(ProcColumn::ReadPerSecond),
            "write" | "w/s" | "wps" => Some(ProcColumn::WritePerSecond),
            "tread" | "t.read" => Some(ProcColumn::TotalRead),
            "twrite" | "t.write" => Some(ProcColumn::TotalWrite),
            "state" => Some(ProcColumn::State),
            "user" => Some(ProcColumn::User),
            "time" => Some(ProcColumn::Time),
            #[cfg(unix)]
            "nice" => Some(ProcColumn::Nice),
            "priority" => Some(ProcColumn::Priority),
            #[cfg(feature = "gpu")]
            "gmem" | "gmem%" => Some(ProcColumn::GpuMemPercent),
            #[cfg(feature = "gpu")]
            "gpu%" => Some(ProcColumn::GpuUtilPercent),
            _ => None,
        }
    }
}

impl<'de> Deserialize<'de> for ProcColumn {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let value = String::deserialize(deserializer)?;
        ProcColumn::parse_column_name(&value)
            .ok_or_else(|| serde::de::Error::custom("doesn't match any process column name"))
    }
}

impl From<&ProcColumn> for ProcWidgetColumn {
    fn from(value: &ProcColumn) -> Self {
        match value {
            ProcColumn::Pid | ProcColumn::Count => ProcWidgetColumn::PidOrCount,
            ProcColumn::Name | ProcColumn::Command => ProcWidgetColumn::ProcNameOrCommand,
            ProcColumn::CpuPercent => ProcWidgetColumn::Cpu,
            ProcColumn::MemPercent | ProcColumn::MemValue => ProcWidgetColumn::Mem,
            ProcColumn::VirtualMem => ProcWidgetColumn::VirtualMem,
            ProcColumn::ReadPerSecond => ProcWidgetColumn::ReadPerSecond,
            ProcColumn::WritePerSecond => ProcWidgetColumn::WritePerSecond,
            ProcColumn::TotalRead => ProcWidgetColumn::TotalRead,
            ProcColumn::TotalWrite => ProcWidgetColumn::TotalWrite,
            ProcColumn::State => ProcWidgetColumn::State,
            ProcColumn::User => ProcWidgetColumn::User,
            ProcColumn::Time => ProcWidgetColumn::Time,
            ProcColumn::Priority => ProcWidgetColumn::Priority,
            #[cfg(unix)]
            ProcColumn::Nice => ProcWidgetColumn::Nice,
            #[cfg(feature = "gpu")]
            ProcColumn::GpuMemPercent | ProcColumn::GpuMemValue => ProcWidgetColumn::GpuMem,
            #[cfg(feature = "gpu")]
            ProcColumn::GpuUtilPercent => ProcWidgetColumn::GpuUtil,
        }
    }
}
