1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use super::types::{get_request_size, Arena, CardEntry, Rarity, REQUEST_FREQUENCY};
use anyhow::{bail, Result};
use chrono::{DateTime, Duration, Local};
use std::cmp;
use thiserror::Error;

#[derive(Error, Debug)]
enum MyError {
    #[error("One or more cards have missing values")]
    MissingCalculatedValues,
}

#[derive(PartialEq, Clone)]
pub struct CardData {
    pub cards_remaining: usize,
    pub requests_remaining: usize,
    pub weeks_remaining: f64,
    pub days_remaining: f64,
    pub done_on: DateTime<Local>,
    pub days_in_order: Option<f64>,
    pub done_in_order_on: Option<DateTime<Local>>,
}

impl CardEntry {
    pub fn calc_remaining(&self, arena: Option<&Arena>) -> Option<CardData> {
        // Cannot request legendary cards
        if self.rarity == Rarity::Legendary {
            return None;
        }

        // The arena the user is in (default to the LegendaryArena)
        let request_size = get_request_size(&arena.unwrap_or(&Arena::LegendaryArena));

        let cards_remaining = if self.get_needed() < self.have {
            0
        } else {
            self.get_needed() - self.have
        };

        let requests_remaining = (cards_remaining as f64
            / if self.rarity == Rarity::Common {
                request_size.common as f64
            } else {
                request_size.rare as f64
            })
        .ceil() as usize;

        let weeks_remaining = requests_remaining as f64
            / match self.rarity {
                Rarity::Common => REQUEST_FREQUENCY.common,
                Rarity::Rare => REQUEST_FREQUENCY.rare,
                Rarity::Epic => REQUEST_FREQUENCY.epic,
                Rarity::Legendary => REQUEST_FREQUENCY.legendary, // Unreachable
            } as f64;

        let days_remaining = weeks_remaining * 7 as f64;

        let done_on =
            Local::now().checked_add_signed(Duration::days(days_remaining.ceil() as i64))?;

        Some(CardData {
            cards_remaining,
            requests_remaining,
            weeks_remaining,
            days_remaining,
            done_on,
            days_in_order: None,
            done_in_order_on: None,
        })
    }

    pub fn compute_all(list: &mut Vec<Self>, arena: Option<&Arena>) {
        for card in list {
            card.computed = card.calc_remaining(arena);
        }
    }

    /// Custom order algorithm for sorting CardEntries by days
    //  FnMut(&Self, &Self) -> cmp::Ordering
    pub fn sort_by_remaining(
        arena: Option<&Arena>,
    ) -> Box<dyn FnMut(&Self, &Self) -> cmp::Ordering + '_> {
        Box::new(move |a: &Self, b: &Self| {
            // Handle legendary cards
            if a.rarity == Rarity::Legendary {
                if b.rarity == Rarity::Legendary {
                    return cmp::Ordering::Equal;
                } else {
                    return cmp::Ordering::Greater;
                }
            }
            if b.rarity == Rarity::Legendary {
                return cmp::Ordering::Less;
            }

            let get_remaining = |card: &CardEntry| {
                card.computed
                    .clone()
                    .unwrap_or_else(|| card.calc_remaining(arena).unwrap())
                    .days_remaining
            };

            // Compare the cards
            get_remaining(a).partial_cmp(&get_remaining(b)).unwrap()
        })
    }

    pub fn sum_all(list: &mut Vec<Self>) -> Result<()> {
        let mut prev_time_regular = 0.;
        let mut prev_time_epic = 0.;

        for card in list {
            // Handle cards according to their respective rarities
            let prev_time = match card.rarity {
                Rarity::Common | Rarity::Rare => &mut prev_time_regular,
                Rarity::Epic => &mut prev_time_epic,
                Rarity::Legendary => {
                    // Skip legendary cards
                    continue;
                }
            };

            if let Some(data) = &mut card.computed {
                let current_time = data.days_remaining + *prev_time;

                data.done_in_order_on = Some(
                    Local::now()
                        .checked_add_signed(Duration::days(current_time.ceil() as i64))
                        .ok_or(MyError::MissingCalculatedValues)?,
                );

                data.days_in_order = Some(current_time);
                *prev_time = current_time;
            } else {
                bail!(MyError::MissingCalculatedValues);
            }
        }

        Ok(())
    }
}