use crate::entities::{DateTime, Duration};
use derive_more::Deref;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;

#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub(crate) struct RunningWork {
    pub(crate) start: DateTime,
    pub(crate) subject: Option<String>,
    pub(crate) tags: Vec<String>,
}

impl RunningWork {
    pub(crate) fn duration(&self) -> Duration {
        DateTime::now() - self.start
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, Ord, Eq, PartialEq, PartialOrd)]
pub(crate) struct WorkTime {
    start: DateTime,
    end: DateTime,
}

impl WorkTime {
    pub(crate) fn new(start: DateTime, end: DateTime) -> Self {
        // use WorkingScheme::check() before calling
        assert!(start <= end);
        Self { start, end }
    }

    pub(crate) fn set(&mut self, start: DateTime, end: DateTime) {
        // use WorkingScheme::check() before calling
        assert!(start <= end);
        self.start = start;
        self.end = end;
    }

    pub(crate) fn start(&self) -> DateTime {
        self.start
    }

    pub(crate) fn end(&self) -> DateTime {
        self.end
    }

    pub(crate) fn duration(&self) -> Duration {
        self.end - self.start
    }

    pub(crate) fn duration_intersect(&self, from: DateTime, to: DateTime) -> Duration {
        assert!(from <= to);
        let start = std::cmp::max(self.start, from);
        let end = std::cmp::min(self.end, to);
        if start <= end {
            end - start
        } else {
            Duration::zero()
        }
    }
}

#[derive(Deref, Debug, Clone, Default, Serialize, Deserialize)]
pub(crate) struct Work {
    #[deref]
    pub(crate) work_time: WorkTime,
    pub(crate) subject: String,
    pub(crate) tags: HashSet<String>,
    pub(crate) deleted: bool,
}

impl PartialEq for Work {
    fn eq(&self, other: &Self) -> bool {
        self.work_time == other.work_time
    }
}

impl Eq for Work {}

impl PartialOrd for Work {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Work {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.work_time.cmp(&other.work_time)
    }
}
