Documentation
/*
==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--

Dia-Time

Copyright (C) 2018-2022, 2024  Anonymous

There are several releases over multiple years,
they are listed as ranges, such as: "2018-2022".

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
*/

//! # Month

use {
    core::{
        fmt::{self, Debug, Display, Formatter},
        ops::Deref,
        str::FromStr,
    },
    crate::{Error, Result as CrateResult},
};

mod tests;

pub (crate) const END_OF_FEBRUARY_IN_LEAP_YEARS: u8 = 29;
pub (crate) const END_OF_FEBRUARY_IN_COMMON_YEARS: u8 = 28;

const SECONDS_OF_28_DAYS: i64 = (crate::DAY * 28) as i64;
const SECONDS_OF_29_DAYS: i64 = (crate::DAY * 29) as i64;
const SECONDS_OF_30_DAYS: i64 = (crate::DAY * 30) as i64;
const SECONDS_OF_31_DAYS: i64 = (crate::DAY * 31) as i64;

/// # Month
///
/// ## Notes
///
/// - The months' names are English only. That applies to implementations of [`FromStr`][trait:core/str/FromStr],
///   [`Deref<Target=str>`][trait:core/ops/Deref]...
/// - Implementation of `Deref<Target=str>` is same as [`Display`][trait:core/fmt/Display]'s, but it's faster because it provides references to
///   static strings.
///
/// [trait:core/fmt/Display]: https://doc.rust-lang.org/core/fmt/trait.Display.html
/// [trait:core/ops/Deref]: https://doc.rust-lang.org/core/ops/trait.Deref.html
/// [trait:core/str/FromStr]: https://doc.rust-lang.org/core/str/trait.FromStr.html
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Copy)]
pub enum Month {

    /// # January,
    January,

    /// # February,
    February,

    /// # March,
    March,

    /// # April,
    April,

    /// # May,
    May,

    /// # June,
    June,

    /// # July,
    July,

    /// # August,
    August,

    /// # September,
    September,

    /// # October,
    October,

    /// # November,
    November,

    /// # December,
    December,

}

impl Month {

    /// # Returns ordinal number of this month
    ///
    /// January will be `1`, February will be `2`...
    pub fn order(&self) -> u8 {
        match self {
            Month::January => 1,
            Month::February => 2,
            Month::March => 3,
            Month::April => 4,
            Month::May => 5,
            Month::June => 6,
            Month::July => 7,
            Month::August => 8,
            Month::September => 9,
            Month::October => 10,
            Month::November => 11,
            Month::December => 12,
        }
    }

    /// # Parses month from an ordinal number
    ///
    /// `1` will be January, `2` will be February...
    pub fn try_from_order(order: u8) -> CrateResult<Self> {
        match order {
            1 => Ok(Month::January),
            2 => Ok(Month::February),
            3 => Ok(Month::March),
            4 => Ok(Month::April),
            5 => Ok(Month::May),
            6 => Ok(Month::June),
            7 => Ok(Month::July),
            8 => Ok(Month::August),
            9 => Ok(Month::September),
            10 => Ok(Month::October),
            11 => Ok(Month::November),
            12 => Ok(Month::December),
            _ => Err(err!("Invalid month: {}", order)),
        }
    }

    /// # Gets next month
    pub fn next(&self) -> Option<Self> {
        match self {
            Month::January => Some(Month::February),
            Month::February => Some(Month::March),
            Month::March => Some(Month::April),
            Month::April => Some(Month::May),
            Month::May => Some(Month::June),
            Month::June => Some(Month::July),
            Month::July => Some(Month::August),
            Month::August => Some(Month::September),
            Month::September => Some(Month::October),
            Month::October => Some(Month::November),
            Month::November => Some(Month::December),
            Month::December => None,
        }
    }

    /// # Gets next month
    ///
    /// Next of December will be January.
    pub fn wrapping_next(&self) -> Self {
        match self {
            Month::January => Month::February,
            Month::February => Month::March,
            Month::March => Month::April,
            Month::April => Month::May,
            Month::May => Month::June,
            Month::June => Month::July,
            Month::July => Month::August,
            Month::August => Month::September,
            Month::September => Month::October,
            Month::October => Month::November,
            Month::November => Month::December,
            Month::December => Month::January,
        }
    }

    /// # Gets last month
    pub fn last(&self) -> Option<Self> {
        match self {
            Month::January => None,
            Month::February => Some(Month::January),
            Month::March => Some(Month::February),
            Month::April => Some(Month::March),
            Month::May => Some(Month::April),
            Month::June => Some(Month::May),
            Month::July => Some(Month::June),
            Month::August => Some(Month::July),
            Month::September => Some(Month::August),
            Month::October => Some(Month::September),
            Month::November => Some(Month::October),
            Month::December => Some(Month::November),
        }
    }

    /// # Gets last month
    ///
    /// Last of January will be December.
    pub fn wrapping_last(&self) -> Self {
        match self {
            Month::January => Month::December,
            Month::February => Month::January,
            Month::March => Month::February,
            Month::April => Month::March,
            Month::May => Month::April,
            Month::June => Month::May,
            Month::July => Month::June,
            Month::August => Month::July,
            Month::September => Month::August,
            Month::October => Month::September,
            Month::November => Month::October,
            Month::December => Month::November,
        }
    }

    /// # Converts self into Unix value
    pub (crate) fn to_unix(&self) -> i32 {
        match self {
            Month::January => 0,
            Month::February => 1,
            Month::March => 2,
            Month::April => 3,
            Month::May => 4,
            Month::June => 5,
            Month::July => 6,
            Month::August => 7,
            Month::September => 8,
            Month::October => 9,
            Month::November => 10,
            Month::December => 11,
        }
    }

    /// # Tries to convert a Unix value into self
    #[cfg(test)]
    #[cfg(not(windows))]
    pub (crate) fn try_from_unix(tm_mon: i32) -> CrateResult<Self> {
        match tm_mon {
            0 => Ok(Month::January),
            1 => Ok(Month::February),
            2 => Ok(Month::March),
            3 => Ok(Month::April),
            4 => Ok(Month::May),
            5 => Ok(Month::June),
            6 => Ok(Month::July),
            7 => Ok(Month::August),
            8 => Ok(Month::September),
            9 => Ok(Month::October),
            10 => Ok(Month::November),
            11 => Ok(Month::December),
            _ => Err(err!("Invalid Unix month: {tm_mon}", tm_mon=tm_mon)),
        }
    }

}

impl Deref for Month {

    type Target = str;

    fn deref(&self) -> &Self::Target {
        match self {
            Month::January => "January",
            Month::February => "February",
            Month::March => "March",
            Month::April => "April",
            Month::May => "May",
            Month::June => "June",
            Month::July => "July",
            Month::August => "August",
            Month::September => "September",
            Month::October => "October",
            Month::November => "November",
            Month::December => "December",
        }
    }

}

impl Display for Month {

    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        f.write_str(self)
    }

}

impl FromStr for Month {

    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.eq_ignore_ascii_case(&*Month::January) {
            Ok(Month::January)
        } else if s.eq_ignore_ascii_case(&*Month::February) {
            Ok(Month::February)
        } else if s.eq_ignore_ascii_case(&*Month::March) {
            Ok(Month::March)
        } else if s.eq_ignore_ascii_case(&*Month::April) {
            Ok(Month::April)
        } else if s.eq_ignore_ascii_case(&*Month::May) {
            Ok(Month::May)
        } else if s.eq_ignore_ascii_case(&*Month::June) {
            Ok(Month::June)
        } else if s.eq_ignore_ascii_case(&*Month::July) {
            Ok(Month::July)
        } else if s.eq_ignore_ascii_case(&*Month::August) {
            Ok(Month::August)
        } else if s.eq_ignore_ascii_case(&*Month::September) {
            Ok(Month::September)
        } else if s.eq_ignore_ascii_case(&*Month::October) {
            Ok(Month::October)
        } else if s.eq_ignore_ascii_case(&*Month::November) {
            Ok(Month::November)
        } else if s.eq_ignore_ascii_case(&*Month::December) {
            Ok(Month::December)
        } else {
            Err(err!("Unknown month: {:?}", s))
        }
    }

}

/// # Gets last day of month
pub (crate) fn last_day_of_month(month: &Month, leap_year: bool) -> u8 {
    match month {
        Month::January => 31,
        Month::February => if leap_year { END_OF_FEBRUARY_IN_LEAP_YEARS } else { END_OF_FEBRUARY_IN_COMMON_YEARS },
        Month::March => 31,
        Month::April => 30,
        Month::May => 31,
        Month::June => 30,
        Month::July => 31,
        Month::August => 31,
        Month::September => 30,
        Month::October => 31,
        Month::November => 30,
        Month::December => 31,
    }
}

/// # Gets seconds of month
pub (crate) fn seconds_of_month(month: &Month, leap_year: bool) -> i64 {
    match month {
        Month::January | Month::March | Month::May | Month::July | Month::August | Month::October | Month::December => SECONDS_OF_31_DAYS,
        Month::February => if leap_year { SECONDS_OF_29_DAYS } else { SECONDS_OF_28_DAYS },
        Month::April | Month::June | Month::September | Month::November => SECONDS_OF_30_DAYS,
    }
}