acick 0.0.9

Tools for programming contests
Documentation
use std::cmp::max;
use std::fmt;
use std::io::Write as _;
use std::time::Duration;

use console::StyledObject;
use getset::CopyGetters;
use serde::{Deserialize, Serialize};

use crate::console::{
    sty_dim, sty_g, sty_g_rev, sty_g_under, sty_none, sty_r, sty_r_rev, sty_r_under, sty_y,
    sty_y_rev, sty_y_under,
};
use crate::judge::diff::TextDiff;
use crate::{Console, Error, Result};

#[derive(
    Serialize, Deserialize, AsRefStr, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[serde(rename_all = "UPPERCASE")]
pub enum StatusKind {
    #[strum(serialize = " A C ")]
    Ac,
    #[strum(serialize = " W A ")]
    Wa,
    #[strum(serialize = " TLE ")]
    Tle,
    #[strum(serialize = " R E ")]
    Re,
}

impl StatusKind {
    fn sty<D>(self, val: D) -> StyledObject<D> {
        match self {
            Self::Ac => sty_g(val),
            Self::Wa => sty_r(val),
            Self::Tle => sty_y(val),
            Self::Re => sty_y(val),
        }
    }

    fn sty_under<D>(self, val: D) -> StyledObject<D> {
        match self {
            Self::Ac => sty_g_under(val),
            Self::Wa => sty_r_under(val),
            Self::Tle => sty_y_under(val),
            Self::Re => sty_y_under(val),
        }
    }

    fn sty_under_if<D>(self, val: D, condition: bool) -> StyledObject<D> {
        if condition {
            self.sty_under(val)
        } else {
            sty_none(val)
        }
    }

    fn sty_rev<D>(self, val: D) -> StyledObject<D> {
        match self {
            Self::Ac => sty_g_rev(val),
            Self::Wa => sty_r_rev(val),
            Self::Tle => sty_y_rev(val),
            Self::Re => sty_y_rev(val),
        }
    }
}

impl fmt::Display for StatusKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.sty_rev(self.as_ref()))
    }
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "UPPERCASE", tag = "kind")]
enum StatusInner {
    Ac,
    Wa { diff: TextDiff },
    Tle,
    Re { reason: String },
}

impl StatusInner {
    fn describe(&self, cnsl: &mut Console) -> Result<()> {
        match self {
            Self::Ac => {}
            Self::Wa { diff } => writeln!(cnsl, "{}", diff)?,
            Self::Tle => {}
            Self::Re { reason } => writeln!(cnsl, "{}", reason)?,
        }
        Ok(())
    }

    fn to_kind(&self) -> StatusKind {
        match self {
            Self::Ac { .. } => StatusKind::Ac,
            Self::Wa { .. } => StatusKind::Wa,
            Self::Tle => StatusKind::Tle,
            Self::Re { .. } => StatusKind::Re,
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Status {
    sample_name: String,
    #[serde(with = "humantime_serde")]
    elapsed: Duration,
    #[serde(flatten)]
    inner: StatusInner,
}

impl Status {
    pub fn ac(sample_name: String, elapsed: Duration) -> Self {
        Self {
            sample_name,
            elapsed,
            inner: StatusInner::Ac,
        }
    }

    pub fn wa(sample_name: String, elapsed: Duration, diff: TextDiff) -> Self {
        Self {
            sample_name,
            elapsed,
            inner: StatusInner::Wa { diff },
        }
    }

    pub fn tle(sample_name: String, elapsed: Duration) -> Self {
        Self {
            sample_name,
            elapsed,
            inner: StatusInner::Tle,
        }
    }

    pub fn re(sample_name: String, elapsed: Duration, err: Error) -> Self {
        Self {
            sample_name,
            elapsed,
            inner: StatusInner::Re {
                reason: format!("{:?}\n", err),
            },
        }
    }

    pub fn kind(&self) -> StatusKind {
        self.inner.to_kind()
    }

    pub fn describe(&self, cnsl: &mut Console) -> Result<()> {
        self.inner.describe(cnsl)
    }
}

impl fmt::Display for Status {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let elapsed = format!("({:>4}ms)", self.elapsed.as_millis());
        let elapsed = if self.kind() == StatusKind::Tle {
            StatusKind::Tle.sty(elapsed)
        } else {
            sty_dim(elapsed)
        };
        write!(f, "{} {}", self.kind(), elapsed)
    }
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
struct StatusCount {
    ac: usize,
    wa: usize,
    tle: usize,
    re: usize,
}

impl StatusCount {
    fn new() -> Self {
        Self {
            ac: 0,
            wa: 0,
            tle: 0,
            re: 0,
        }
    }

    fn add(&mut self, kind: StatusKind) -> &mut Self {
        match kind {
            StatusKind::Ac => self.ac += 1,
            StatusKind::Wa => self.wa += 1,
            StatusKind::Tle => self.tle += 1,
            StatusKind::Re => self.re += 1,
        }
        self
    }

    fn total(&self) -> usize {
        self.ac + self.wa + self.tle + self.re
    }
}

#[derive(Serialize, Deserialize, CopyGetters, Debug, Clone, PartialEq, Eq, Hash)]
pub struct TotalStatus {
    #[get_copy = "pub"]
    kind: StatusKind,
    count: StatusCount,
    statuses: Vec<Status>,
}

impl TotalStatus {
    pub fn new(statuses: Vec<Status>) -> Self {
        let (kind, count) = statuses.iter().fold(
            (StatusKind::Ac, StatusCount::new()),
            |(kind, mut count), status| {
                count.add(status.kind());
                (max(kind, status.kind()), count)
            },
        );

        Self {
            kind,
            count,
            statuses,
        }
    }
}

impl fmt::Display for TotalStatus {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let StatusCount { ac, wa, tle, re } = self.count;
        write!(
            f,
            "{} (AC: {:>2}/{t:>2}, WA: {:>2}/{t:>2}, TLE: {:>2}/{t:>2}, RE: {:>2}/{t:>2})",
            self.kind,
            ac,
            StatusKind::Wa.sty_under_if(wa, wa > 0),
            StatusKind::Tle.sty_under_if(tle, tle > 0),
            StatusKind::Re.sty_under_if(re, re > 0),
            t = self.count.total()
        )
    }
}